generic <UpgradeScreen> component (#6594)
* first attempt of <UpgradeScreen> * changes to icons * reverted changes back to initial state, needs fix: teams not showing * WIP * Fix weird reactnode error * Fix loading text * added upgradeTip to routing forms * icon colors * create and use hook to check if user has team plan * use useTeamPlan for upgradeTeamsBadge * replace huge svg with compressed jpeg * responsive fixes * Update packages/ui/components/badge/UpgradeTeamsBadge.tsx Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> * Give team plan features to E2E tests * Allow option to make a user part of team int ests * Remove flash of paywall for team user * Add team user for typeform tests as well Co-authored-by: Peer Richelsen <peer@cal.com> Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
parent
9602ac29fb
commit
1d792a2c6c
|
@ -4,6 +4,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasTeamPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import {
|
||||
|
@ -49,7 +50,8 @@ const AppearanceView = () => {
|
|||
const session = useSession();
|
||||
const utils = trpc.useContext();
|
||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||
const { data: dataHasTeamPlan, isLoading: isLoadingHasTeamPlan } = trpc.viewer.teams.hasTeamPlan.useQuery();
|
||||
|
||||
const { isLoading: isTeamPlanStatusLoading, hasTeamPlan } = useHasTeamPlan();
|
||||
|
||||
const formMethods = useForm({
|
||||
defaultValues: {
|
||||
|
@ -74,7 +76,7 @@ const AppearanceView = () => {
|
|||
},
|
||||
});
|
||||
|
||||
if (isLoading || isLoadingHasTeamPlan)
|
||||
if (isLoading || isTeamPlanStatusLoading)
|
||||
return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
|
||||
|
||||
if (!user) return null;
|
||||
|
@ -182,18 +184,18 @@ const AppearanceView = () => {
|
|||
<p className="font-semibold ltr:mr-2 rtl:ml-2">
|
||||
{t("disable_cal_branding", { appName: APP_NAME })}
|
||||
</p>
|
||||
{!dataHasTeamPlan?.hasTeamPlan && <UpgradeTeamsBadge />}
|
||||
<UpgradeTeamsBadge />
|
||||
</div>
|
||||
<p className="mt-0.5 text-gray-600">{t("removes_cal_branding", { appName: APP_NAME })}</p>
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Switch
|
||||
id="hideBranding"
|
||||
disabled={!dataHasTeamPlan?.hasTeamPlan}
|
||||
disabled={!hasTeamPlan}
|
||||
onCheckedChange={(checked) =>
|
||||
formMethods.setValue("hideBranding", checked, { shouldDirty: true })
|
||||
}
|
||||
checked={!dataHasTeamPlan?.hasTeamPlan ? false : value}
|
||||
checked={hasTeamPlan ? value : false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { Page, WorkerInfo } from "@playwright/test";
|
||||
import type Prisma from "@prisma/client";
|
||||
import { Prisma as PrismaType } from "@prisma/client";
|
||||
import { Prisma as PrismaType, MembershipRole } from "@prisma/client";
|
||||
import { hash } from "bcryptjs";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
|
@ -34,6 +34,27 @@ const seededForm = {
|
|||
|
||||
type UserWithIncludes = PrismaType.UserGetPayload<typeof userWithEventTypes>;
|
||||
|
||||
const createTeamAndAddUser = async ({ user }: { user: { id: number; role?: MembershipRole } }) => {
|
||||
const team = await prisma.team.create({
|
||||
data: {
|
||||
name: "",
|
||||
slug: `team-${Date.now()}`,
|
||||
},
|
||||
});
|
||||
if (!team) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { role = MembershipRole.OWNER, id: userId } = user;
|
||||
await prisma.membership.create({
|
||||
data: {
|
||||
teamId: team.id,
|
||||
userId,
|
||||
role: role,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// creates a user fixture instance and stores the collection
|
||||
export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
|
||||
const store = { users: [], page } as { users: UserFixture[]; page: typeof page };
|
||||
|
@ -42,6 +63,7 @@ export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
|
|||
opts?: CustomUserOpts | null,
|
||||
scenario: {
|
||||
seedRoutingForms?: boolean;
|
||||
hasTeam?: true;
|
||||
} = {}
|
||||
) => {
|
||||
const _user = await prisma.user.create({
|
||||
|
@ -193,6 +215,9 @@ export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
|
|||
where: { id: _user.id },
|
||||
include: userIncludes,
|
||||
});
|
||||
if (scenario.hasTeam) {
|
||||
await createTeamAndAddUser({ user: { id: user.id, role: "OWNER" } });
|
||||
}
|
||||
const userFixture = createUserFixture(user, store.page!);
|
||||
store.users.push(userFixture);
|
||||
return userFixture;
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
|
@ -1238,6 +1238,7 @@
|
|||
"to": "To",
|
||||
"workflow_turned_on_successfully": "{{workflowName}} workflow turned {{offOn}} successfully",
|
||||
"download_responses": "Download Responses",
|
||||
"download_responses_description": "Download all responses to your form in CSV format.",
|
||||
"download": "Download",
|
||||
"create_your_first_form": "Create your first form",
|
||||
"create_your_first_form_description": "With Routing Forms you can ask qualifying questions and route to the correct person or event type.",
|
||||
|
@ -1280,6 +1281,8 @@
|
|||
"routing_forms_send_email_owner": "Send Email to Owner",
|
||||
"routing_forms_send_email_owner_description": "Sends an email to the owner when the form is submitted",
|
||||
"add_new_form": "Add new form",
|
||||
"create_your_first_route": "Create your first route",
|
||||
"route_to_the_right_person": "Route to the right person based on the answers to your form",
|
||||
"form_description": "Create your form to route a booker",
|
||||
"copy_link_to_form": "Copy link to form",
|
||||
"theme": "Theme",
|
||||
|
@ -1514,5 +1517,9 @@
|
|||
"install_google_meet": "Install Google Meet",
|
||||
"install_google_calendar": "Install Google Calendar",
|
||||
"sender_name": "Sender name",
|
||||
"no_recordings_found": "No recordings found"
|
||||
"no_recordings_found": "No recordings found",
|
||||
"reporting": "Reporting",
|
||||
"reporting_feature": "See all incoming from data and download it as a CSV",
|
||||
"teams_plan_required": "Teams plan required",
|
||||
"routing_forms_are_a_great_way": "Routing forms are a great way to route your incoming leads to the right person. Upgrade to a Teams plan to access this feature."
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
// TODO: i18n
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import SkeletonLoaderTeamList from "@calcom/features/ee/teams/components/SkeletonloaderTeamList";
|
||||
import Shell, { ShellMain } from "@calcom/features/shell/Shell";
|
||||
import { UpgradeTip } from "@calcom/features/tips";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import useApp from "@calcom/lib/hooks/useApp";
|
||||
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasTeamPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { AppGetServerSidePropsContext, AppPrisma, AppUser } from "@calcom/types/AppGetServerSideProps";
|
||||
|
@ -15,6 +20,7 @@ import {
|
|||
List,
|
||||
ListLinkItem,
|
||||
Tooltip,
|
||||
Button,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
@ -27,12 +33,47 @@ export default function RoutingForms({
|
|||
appUrl,
|
||||
}: inferSSRProps<typeof getServerSideProps> & { appUrl: string }) {
|
||||
const { t } = useLocale();
|
||||
const { data: forms } = trpc.viewer.appRoutingForms.forms.useQuery(undefined, {
|
||||
const { hasTeamPlan } = useHasTeamPlan();
|
||||
|
||||
const { data: forms, isLoading } = trpc.viewer.appRoutingForms.forms.useQuery(undefined, {
|
||||
initialData: forms_,
|
||||
});
|
||||
|
||||
const { data: typeformApp } = useApp("typeform");
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <Icon.FiFileText className="h-5 w-5 text-orange-500" />,
|
||||
title: t("create_your_first_form"),
|
||||
description: t("create_your_first_form_description"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiShuffle className="h-5 w-5 text-lime-500" />,
|
||||
title: t("create_your_first_route"),
|
||||
description: t("route_to_the_right_person"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiBarChart className="h-5 w-5 text-blue-500" />,
|
||||
title: t("reporting"),
|
||||
description: t("reporting_feature"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiCheckCircle className="h-5 w-5 text-teal-500" />,
|
||||
title: t("test_routing_form"),
|
||||
description: t("test_preview_description"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiMail className="h-5 w-5 text-yellow-500" />,
|
||||
title: t("routing_forms_send_email_owner"),
|
||||
description: t("routing_forms_send_email_owner_description"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiDownload className="h-5 w-5 text-violet-500" />,
|
||||
title: t("download_responses"),
|
||||
description: t("download_responses_description"),
|
||||
},
|
||||
];
|
||||
|
||||
function NewFormButton() {
|
||||
return (
|
||||
<FormAction
|
||||
|
@ -46,7 +87,33 @@ export default function RoutingForms({
|
|||
);
|
||||
}
|
||||
return (
|
||||
<ShellMain heading="Routing Forms" CTA={<NewFormButton />} subtitle={t("routing_forms_description")}>
|
||||
<ShellMain
|
||||
heading="Routing Forms"
|
||||
CTA={hasTeamPlan && <NewFormButton />}
|
||||
subtitle={t("routing_forms_description")}>
|
||||
<UpgradeTip
|
||||
dark
|
||||
title={t("teams_plan_required")}
|
||||
description={t("routing_forms_are_a_great_way")}
|
||||
features={features}
|
||||
background="/routing-form-banner-background.jpg"
|
||||
isParentLoading={isLoading && <SkeletonLoaderTeamList />}
|
||||
buttons={
|
||||
<div className="space-y-2 rtl:space-x-reverse sm:space-x-2">
|
||||
<ButtonGroup>
|
||||
<Button color="secondary" href={`${WEBAPP_URL}/settings/teams/new`}>
|
||||
{t("upgrade")}
|
||||
</Button>
|
||||
<Button
|
||||
color="minimal"
|
||||
className="!bg-transparent text-white opacity-50 hover:opacity-100"
|
||||
href="https://go.cal.com/teams-video"
|
||||
target="_blank">
|
||||
{t("learn_more")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
}>
|
||||
<FormActionsProvider appUrl={appUrl}>
|
||||
<div className="-mx-4 md:-mx-8">
|
||||
<div className="mb-10 w-full px-4 pb-2 sm:px-6 md:px-8">
|
||||
|
@ -177,6 +244,7 @@ export default function RoutingForms({
|
|||
</div>
|
||||
</div>
|
||||
</FormActionsProvider>
|
||||
</UpgradeTip>
|
||||
</ShellMain>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -118,7 +118,12 @@ test.describe("Routing Forms", () => {
|
|||
|
||||
// TODO: How to install the app just once?
|
||||
test.beforeEach(async ({ page, users }) => {
|
||||
const user = await users.create({ username: "routing-forms" });
|
||||
const user = await users.create(
|
||||
{ username: "routing-forms" },
|
||||
{
|
||||
hasTeam: true,
|
||||
}
|
||||
);
|
||||
await user.login();
|
||||
// Install app
|
||||
await page.goto(`/apps/routing-forms`);
|
||||
|
@ -148,7 +153,10 @@ test.describe("Routing Forms", () => {
|
|||
users: Fixtures["users"];
|
||||
page: Page;
|
||||
}) {
|
||||
const user = await users.create({ username: "routing-forms" }, { seedRoutingForms: true });
|
||||
const user = await users.create(
|
||||
{ username: "routing-forms" },
|
||||
{ seedRoutingForms: true, hasTeam: true }
|
||||
);
|
||||
await user.login();
|
||||
// Install app
|
||||
await page.goto(`/apps/routing-forms`);
|
||||
|
|
|
@ -8,7 +8,12 @@ import { CAL_URL } from "@calcom/lib/constants";
|
|||
import { Fixtures, test } from "@calcom/web/playwright/lib/fixtures";
|
||||
|
||||
const installApps = async (page: Page, users: Fixtures["users"]) => {
|
||||
const user = await users.create({ username: "routing-forms" });
|
||||
const user = await users.create(
|
||||
{ username: "routing-forms" },
|
||||
{
|
||||
hasTeam: true,
|
||||
}
|
||||
);
|
||||
await user.login();
|
||||
await page.goto(`/apps/routing-forms`);
|
||||
await page.click('[data-testid="install-app-button"]');
|
||||
|
|
|
@ -3,10 +3,10 @@ import { useState } from "react";
|
|||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import isCalcom from "@calcom/lib/isCalcom";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Alert, Button, ButtonGroup, EmptyScreen, Icon } from "@calcom/ui";
|
||||
import { Alert, Button, ButtonGroup, Icon } from "@calcom/ui";
|
||||
|
||||
import { UpgradeTip } from "../../../tips";
|
||||
import SkeletonLoaderTeamList from "./SkeletonloaderTeamList";
|
||||
import TeamList from "./TeamList";
|
||||
|
||||
|
@ -21,36 +21,35 @@ export function TeamsListing() {
|
|||
});
|
||||
|
||||
const teams = data?.filter((m) => m.accepted) || [];
|
||||
const invites = data?.filter((m) => !m.accepted) || [];
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <Icon.FiUsers className="h-5 w-5 text-gray-700" />,
|
||||
icon: <Icon.FiUsers className="h-5 w-5 text-red-500" />,
|
||||
title: t("collective_scheduling"),
|
||||
description: t("make_it_easy_to_book"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiRefreshCcw className="h-5 w-5 text-gray-700" />,
|
||||
icon: <Icon.FiRefreshCcw className="h-5 w-5 text-blue-500" />,
|
||||
title: t("round_robin"),
|
||||
description: t("find_the_best_person"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiUserPlus className="h-5 w-5 text-gray-700" />,
|
||||
icon: <Icon.FiUserPlus className="h-5 w-5 text-green-500" />,
|
||||
title: t("fixed_round_robin"),
|
||||
description: t("add_one_fixed_attendee"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiMail className="h-5 w-5 text-gray-700" />,
|
||||
icon: <Icon.FiMail className="h-5 w-5 text-orange-500" />,
|
||||
title: t("sms_attendee_action"),
|
||||
description: t("make_it_easy_to_book"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiVideo className="h-5 w-5 text-gray-700" />,
|
||||
icon: <Icon.FiVideo className="h-5 w-5 text-purple-500" />,
|
||||
title: "Cal Video" + " " + t("recordings_title"),
|
||||
description: t("upgrade_to_access_recordings_description"),
|
||||
},
|
||||
{
|
||||
icon: <Icon.FiEyeOff className="h-5 w-5 text-gray-700" />,
|
||||
icon: <Icon.FiEyeOff className="h-5 w-5 text-indigo-500" />,
|
||||
title: t("disable_cal_branding", { appName: APP_NAME }),
|
||||
description: t("disable_cal_branding_description", { appName: APP_NAME }),
|
||||
},
|
||||
|
@ -59,27 +58,13 @@ export function TeamsListing() {
|
|||
return (
|
||||
<>
|
||||
{!!errorMessage && <Alert severity="error" title={errorMessage} />}
|
||||
{invites.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<h1 className="mb-2 text-lg font-medium">{t("open_invitations")}</h1>
|
||||
<TeamList teams={invites} />
|
||||
</div>
|
||||
)}
|
||||
{isLoading && <SkeletonLoaderTeamList />}
|
||||
{!teams.length && !isLoading && (
|
||||
<>
|
||||
{!isCalcom ? (
|
||||
<div className="-mt-6 rtl:ml-4 md:rtl:ml-0">
|
||||
<div
|
||||
className="flex w-full justify-between overflow-hidden rounded-lg pt-4 pb-10 md:min-h-[295px] md:pt-10"
|
||||
style={{
|
||||
background: "url(/team-banner-background.jpg)",
|
||||
backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",
|
||||
}}>
|
||||
<div className="mt-3 px-8 sm:px-14">
|
||||
<h1 className="font-cal text-3xl">{t("calcom_is_better_with_team")}</h1>
|
||||
<p className="my-4 max-w-sm text-gray-600">{t("add_your_team_members")}</p>
|
||||
<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>
|
||||
<Button color="primary" href={`${WEBAPP_URL}/settings/teams/new`}>
|
||||
|
@ -90,35 +75,9 @@ export function TeamsListing() {
|
|||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 grid-cols-3 md:grid md:gap-4">
|
||||
{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>
|
||||
) : (
|
||||
<EmptyScreen
|
||||
Icon={Icon.FiUsers}
|
||||
headline={t("no_teams")}
|
||||
description={t("no_teams_description")}
|
||||
buttonRaw={
|
||||
<Button color="secondary" href={`${WEBAPP_URL}/settings/teams/new`}>
|
||||
{t("create_team")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{teams.length > 0 && <TeamList teams={teams} />}
|
||||
}>
|
||||
<TeamList teams={teams} />
|
||||
</UpgradeTip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useSession } from "next-auth/react";
|
||||
import { useState } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
|
||||
import useHasTeamPlan from "@calcom/lib/hooks/useHasTeamPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RecordingItemSchema } from "@calcom/prisma/zod-utils";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
|
@ -18,7 +18,6 @@ import {
|
|||
import { Button, showToast, Icon } from "@calcom/ui";
|
||||
|
||||
import RecordingListSkeleton from "./components/RecordingListSkeleton";
|
||||
import UpgradeRecordingBanner from "./components/UpgradeRecordingBanner";
|
||||
|
||||
type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number];
|
||||
|
||||
|
@ -65,7 +64,9 @@ export const ViewRecordingsDialog = (props: IViewRecordingsDialog) => {
|
|||
const { t, i18n } = useLocale();
|
||||
const { isOpenDialog, setIsOpenDialog, booking, timeFormat } = props;
|
||||
const [downloadingRecordingId, setRecordingId] = useState<string | null>(null);
|
||||
const { data: dataHasTeamPlan, isLoading: isLoadingHasTeamPlan } = trpc.viewer.teams.hasTeamPlan.useQuery();
|
||||
|
||||
const { hasTeamPlan, isLoading: isTeamPlanStatusLoading } = useHasTeamPlan();
|
||||
|
||||
const roomName =
|
||||
booking?.references?.find((reference: PartialReference) => reference.type === "daily_video")?.meetingId ??
|
||||
undefined;
|
||||
|
@ -109,7 +110,7 @@ export const ViewRecordingsDialog = (props: IViewRecordingsDialog) => {
|
|||
<DialogHeader title={t("recordings_title")} subtitle={subtitle} />
|
||||
<LicenseRequired>
|
||||
<>
|
||||
{(isLoading || isLoadingHasTeamPlan) && <RecordingListSkeleton />}
|
||||
{(isLoading || isTeamPlanStatusLoading) && <RecordingListSkeleton />}
|
||||
{recordings && "data" in recordings && recordings?.data?.length > 0 && (
|
||||
<div className="flex flex-col gap-3">
|
||||
{recordings.data.map((recording: RecordingItemSchema, index: number) => {
|
||||
|
@ -125,7 +126,7 @@ export const ViewRecordingsDialog = (props: IViewRecordingsDialog) => {
|
|||
{convertSecondsToMs(recording.duration)}
|
||||
</p>
|
||||
</div>
|
||||
{dataHasTeamPlan?.hasTeamPlan ? (
|
||||
{hasTeamPlan ? (
|
||||
<Button
|
||||
StartIcon={Icon.FiDownload}
|
||||
className="ml-4 lg:ml-0"
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import { useMemo } from "react";
|
||||
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 { EmptyScreen, Icon } from "@calcom/ui";
|
||||
|
||||
import TeamList from "../ee/teams/components/TeamList";
|
||||
|
||||
const isCalcom = true;
|
||||
export function UpgradeTip({
|
||||
dark,
|
||||
title,
|
||||
description,
|
||||
background,
|
||||
features,
|
||||
buttons,
|
||||
isParentLoading,
|
||||
children,
|
||||
}: {
|
||||
dark?: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
background: string;
|
||||
features: Array<{ icon: JSX.Element; title: string; description: string }>;
|
||||
buttons?: JSX.Element;
|
||||
/**Chldren renders when the user is in a team */
|
||||
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 (!isCalcom)
|
||||
return <EmptyScreen Icon={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">
|
||||
<div
|
||||
className="flex w-full justify-between overflow-hidden rounded-lg pt-4 pb-10 md:min-h-[295px] md:pt-10"
|
||||
style={{
|
||||
background: `url(${background})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",
|
||||
}}>
|
||||
<div className="mt-3 px-8 sm:px-14">
|
||||
<h1 className={classNames("font-cal text-3xl", dark && "text-white")}>{t(title)}</h1>
|
||||
<p className={classNames("my-4 max-w-sm", dark ? "text-white" : "text-gray-600")}>
|
||||
{t(description)}
|
||||
</p>
|
||||
{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>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
export { default as Tips } from "./Tips";
|
||||
export { UpgradeTip } from "./UpgradeTip";
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
export function useHasTeamPlan() {
|
||||
const hasTeam = trpc.viewer.teams.hasTeamPlan.useQuery();
|
||||
|
||||
return { isLoading: hasTeam.isLoading, hasTeamPlan: hasTeam.data?.hasTeamPlan || false };
|
||||
}
|
||||
|
||||
export default useHasTeamPlan;
|
|
@ -1,5 +1,6 @@
|
|||
import Link from "next/link";
|
||||
|
||||
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasTeamPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import { Tooltip } from "../tooltip";
|
||||
|
@ -7,6 +8,9 @@ import { Badge } from "./Badge";
|
|||
|
||||
export const UpgradeTeamsBadge = function UpgradeTeamsBadge() {
|
||||
const { t } = useLocale();
|
||||
const { hasTeamPlan } = useHasTeamPlan();
|
||||
|
||||
if (hasTeamPlan) return null;
|
||||
|
||||
return (
|
||||
<Tooltip content={t("upgrade_to_enable_feature")}>
|
||||
|
|
Loading…
Reference in New Issue
Block a user