Reusable Upgrade component for teams only features (#6473)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
Carina Wollendorfer 2023-01-13 19:48:19 -05:00 committed by GitHub
parent 443329b99f
commit 1376f0991f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 80 additions and 72 deletions

View File

@ -7,7 +7,6 @@ import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { import {
Badge,
Button, Button,
ColorPicker, ColorPicker,
Form, Form,
@ -17,6 +16,7 @@ import {
SkeletonContainer, SkeletonContainer,
SkeletonText, SkeletonText,
Switch, Switch,
UpgradeTeamsBadge,
} from "@calcom/ui"; } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr"; import { ssrInit } from "@server/lib/ssr";
@ -49,6 +49,7 @@ const AppearanceView = () => {
const session = useSession(); const session = useSession();
const utils = trpc.useContext(); const utils = trpc.useContext();
const { data: user, isLoading } = trpc.viewer.me.useQuery(); const { data: user, isLoading } = trpc.viewer.me.useQuery();
const { data: dataHasTeamPlan, isLoading: isLoadingHasTeamPlan } = trpc.viewer.teams.hasTeamPlan.useQuery();
const formMethods = useForm({ const formMethods = useForm({
defaultValues: { defaultValues: {
@ -73,7 +74,8 @@ const AppearanceView = () => {
}, },
}); });
if (isLoading) return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />; if (isLoading || isLoadingHasTeamPlan)
return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
if (!user) return null; if (!user) return null;
@ -180,18 +182,18 @@ const AppearanceView = () => {
<p className="font-semibold ltr:mr-2 rtl:ml-2"> <p className="font-semibold ltr:mr-2 rtl:ml-2">
{t("disable_cal_branding", { appName: APP_NAME })} {t("disable_cal_branding", { appName: APP_NAME })}
</p> </p>
<Badge variant="gray">{t("pro")}</Badge> {!dataHasTeamPlan?.hasTeamPlan && <UpgradeTeamsBadge />}
</div> </div>
<p className="mt-0.5 text-gray-600">{t("removes_cal_branding", { appName: APP_NAME })}</p> <p className="mt-0.5 text-gray-600">{t("removes_cal_branding", { appName: APP_NAME })}</p>
</div> </div>
<div className="flex-none"> <div className="flex-none">
<Switch <Switch
id="hideBranding" id="hideBranding"
disabled={!session.data?.user.belongsToActiveTeam} disabled={!dataHasTeamPlan?.hasTeamPlan}
onCheckedChange={(checked) => onCheckedChange={(checked) =>
formMethods.setValue("hideBranding", checked, { shouldDirty: true }) formMethods.setValue("hideBranding", checked, { shouldDirty: true })
} }
checked={!session.data?.user.belongsToActiveTeam ? false : value} checked={!dataHasTeamPlan?.hasTeamPlan ? false : value}
/> />
</div> </div>
</div> </div>

View File

@ -1509,5 +1509,6 @@
"using_meet_requires_calendar": "Using Google Meet requires a connected Google Calendar", "using_meet_requires_calendar": "Using Google Meet requires a connected Google Calendar",
"continue_to_install_google_calendar": "Continue to install Google Calendar", "continue_to_install_google_calendar": "Continue to install Google Calendar",
"install_google_meet": "Install Google Meet", "install_google_meet": "Install Google Meet",
"install_google_calendar": "Install Google Calendar" "install_google_calendar": "Install Google Calendar",
"no_recordings_found": "No recordings found"
} }

View File

@ -7,7 +7,14 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RecordingItemSchema } from "@calcom/prisma/zod-utils"; import { RecordingItemSchema } from "@calcom/prisma/zod-utils";
import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { RouterOutputs, trpc } from "@calcom/trpc/react";
import type { PartialReference } from "@calcom/types/EventManager"; import type { PartialReference } from "@calcom/types/EventManager";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui"; import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
UpgradeTeamsBadge,
} from "@calcom/ui";
import { Button, showToast, Icon } from "@calcom/ui"; import { Button, showToast, Icon } from "@calcom/ui";
import RecordingListSkeleton from "./components/RecordingListSkeleton"; import RecordingListSkeleton from "./components/RecordingListSkeleton";
@ -58,9 +65,7 @@ export const ViewRecordingsDialog = (props: IViewRecordingsDialog) => {
const { t, i18n } = useLocale(); const { t, i18n } = useLocale();
const { isOpenDialog, setIsOpenDialog, booking, timeFormat } = props; const { isOpenDialog, setIsOpenDialog, booking, timeFormat } = props;
const [downloadingRecordingId, setRecordingId] = useState<string | null>(null); const [downloadingRecordingId, setRecordingId] = useState<string | null>(null);
const session = useSession(); const { data: dataHasTeamPlan, isLoading: isLoadingHasTeamPlan } = trpc.viewer.teams.hasTeamPlan.useQuery();
const belongsToActiveTeam = session?.data?.user?.belongsToActiveTeam ?? false;
const [showUpgradeBanner, setShowUpgradeBanner] = useState<boolean>(false);
const roomName = const roomName =
booking?.references?.find((reference: PartialReference) => reference.type === "daily_video")?.meetingId ?? booking?.references?.find((reference: PartialReference) => reference.type === "daily_video")?.meetingId ??
undefined; undefined;
@ -103,57 +108,48 @@ export const ViewRecordingsDialog = (props: IViewRecordingsDialog) => {
<DialogContent> <DialogContent>
<DialogHeader title={t("recordings_title")} subtitle={subtitle} /> <DialogHeader title={t("recordings_title")} subtitle={subtitle} />
<LicenseRequired> <LicenseRequired>
{showUpgradeBanner && <UpgradeRecordingBanner />} <>
{!showUpgradeBanner && ( {isLoading && isLoadingHasTeamPlan && <RecordingListSkeleton />}
<> {recordings && "data" in recordings && recordings?.data?.length > 0 && (
{isLoading && <RecordingListSkeleton />} <div className="flex flex-col gap-3">
{recordings && "data" in recordings && recordings?.data?.length > 0 && ( {recordings.data.map((recording: RecordingItemSchema, index: number) => {
<div className="flex flex-col gap-3"> return (
{recordings.data.map((recording: RecordingItemSchema, index: number) => { <div
return ( className="flex w-full items-center justify-between rounded-md border px-4 py-2"
<div key={recording.id}>
className="flex w-full items-center justify-between rounded-md border py-2 px-4" <div className="flex flex-col">
key={recording.id}> <h1 className="text-sm font-semibold">
<div className="flex flex-col"> {t("recording")} {index + 1}
<h1 className="text-sm font-semibold"> </h1>
{t("recording")} {index + 1} <p className="text-sm font-normal text-gray-500">
</h1> {convertSecondsToMs(recording.duration)}
<p className="text-sm font-normal text-gray-500"> </p>
{convertSecondsToMs(recording.duration)}
</p>
</div>
{belongsToActiveTeam ? (
<Button
StartIcon={Icon.FiDownload}
className="ml-4 lg:ml-0"
loading={downloadingRecordingId === recording.id}
onClick={() => handleDownloadClick(recording.id)}>
{t("download")}
</Button>
) : (
<Button
color="secondary"
tooltip={t("recordings_are_part_of_the_teams_plan")}
className="ml-4 lg:ml-0"
onClick={() => setShowUpgradeBanner(true)}>
{t("upgrade")}
</Button>
)}
</div> </div>
); {dataHasTeamPlan?.hasTeamPlan ? (
})} <Button
</div> StartIcon={Icon.FiDownload}
className="ml-4 lg:ml-0"
loading={downloadingRecordingId === recording.id}
onClick={() => handleDownloadClick(recording.id)}>
{t("download")}
</Button>
) : (
<UpgradeTeamsBadge />
)}
</div>
);
})}
</div>
)}
{!isLoading &&
(!recordings ||
(recordings && "total_count" in recordings && recordings?.total_count === 0)) && (
<h1 className="font-semibold">{t("no_recordings_found")}</h1>
)} )}
{!isLoading && </>
(!recordings ||
(recordings && "total_count" in recordings && recordings?.total_count === 0)) && (
<h1 className="font-semibold">No Recordings Found</h1>
)}
</>
)}
</LicenseRequired> </LicenseRequired>
<DialogFooter> <DialogFooter>
<DialogClose onClick={() => setShowUpgradeBanner(false)} className="border" /> <DialogClose className="border" />
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -0,0 +1,18 @@
import Link from "next/link";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Tooltip } from "../tooltip";
import { Badge } from "./Badge";
export const UpgradeTeamsBadge = function UpgradeTeamsBadge() {
const { t } = useLocale();
return (
<Tooltip content={t("upgrade_to_enable_feature")}>
<Link href="/teams">
<Badge variant="gray">{t("upgrade")}</Badge>
</Link>
</Tooltip>
);
};

View File

@ -1,2 +1,4 @@
export { Badge } from "./Badge"; export { Badge } from "./Badge";
export { UpgradeTeamsBadge } from "./UpgradeTeamsBadge";
export type { BadgeProps } from "./Badge"; export type { BadgeProps } from "./Badge";

View File

@ -1,4 +1,3 @@
import { useRouter } from "next/router";
import { import {
components as reactSelectComponents, components as reactSelectComponents,
ControlProps, ControlProps,
@ -13,11 +12,9 @@ import {
} from "react-select"; } from "react-select";
import { classNames } from "@calcom/lib"; import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "../../../components/icon"; import { Icon } from "../../../components/icon";
import { Badge } from "../../badge"; import { UpgradeTeamsBadge } from "../../badge";
import { Tooltip } from "../../tooltip";
export const InputComponent = < export const InputComponent = <
Option, Option,
@ -53,9 +50,6 @@ export const OptionComponent = <
className, className,
...props ...props
}: OptionProps<Option, IsMulti, Group>) => { }: OptionProps<Option, IsMulti, Group>) => {
const { t } = useLocale();
const router = useRouter();
return ( return (
<reactSelectComponents.Option <reactSelectComponents.Option
{...props} {...props}
@ -67,13 +61,7 @@ export const OptionComponent = <
)}> )}>
<> <>
<span className="mr-auto">{props.label}</span> <span className="mr-auto">{props.label}</span>
{(props.data as unknown as ExtendedOption).needsUpgrade && ( {(props.data as unknown as ExtendedOption).needsUpgrade && <UpgradeTeamsBadge />}
<Tooltip content={t("upgrade_to_enable_feature")}>
<button type="button" onClick={() => router.replace("/teams")}>
<Badge variant="gray">{t("upgrade")}</Badge>
</button>
</Tooltip>
)}
{props.isSelected && <Icon.FiCheck className="ml-2 h-4 w-4" />} {props.isSelected && <Icon.FiCheck className="ml-2 h-4 w-4" />}
</> </>
</reactSelectComponents.Option> </reactSelectComponents.Option>

View File

@ -1,6 +1,6 @@
export { Avatar, AvatarGroup } from "./avatar"; export { Avatar, AvatarGroup } from "./avatar";
export type { AvatarProps, AvatarGroupProps } from "./avatar"; export type { AvatarProps, AvatarGroupProps } from "./avatar";
export { Badge } from "./badge"; export { Badge, UpgradeTeamsBadge } from "./badge";
export type { BadgeProps } from "./badge"; export type { BadgeProps } from "./badge";
export { Breadcrumb, BreadcrumbContainer, BreadcrumbItem } from "./breadcrumb"; export { Breadcrumb, BreadcrumbContainer, BreadcrumbItem } from "./breadcrumb";
export { Button, LinkIconButton } from "./button"; export { Button, LinkIconButton } from "./button";

View File

@ -2,6 +2,7 @@ export {
Avatar, Avatar,
AvatarGroup, AvatarGroup,
Badge, Badge,
UpgradeTeamsBadge,
Breadcrumb, Breadcrumb,
BreadcrumbContainer, BreadcrumbContainer,
BreadcrumbItem, BreadcrumbItem,