Reusable Upgrade component for teams only features (#6473)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
parent
443329b99f
commit
1376f0991f
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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";
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -2,6 +2,7 @@ export {
|
||||||
Avatar,
|
Avatar,
|
||||||
AvatarGroup,
|
AvatarGroup,
|
||||||
Badge,
|
Badge,
|
||||||
|
UpgradeTeamsBadge,
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbContainer,
|
BreadcrumbContainer,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user