Latest tweaks (#4482)

This commit is contained in:
Leo Giovanetti 2022-09-15 06:33:34 -03:00 committed by GitHub
parent 0d9d182b56
commit 7a95e714c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 64 additions and 286 deletions

View File

@ -92,11 +92,6 @@ const Component = ({
<header className="px-4 py-2">
<div className="flex items-center">
<h1 className="font-cal text-xl text-gray-900">{name}</h1>
{isProOnly && user?.plan === "FREE" ? (
<Badge className="ml-2" variant="default">
PRO
</Badge>
) : null}
</div>
<h2 className="text-sm text-gray-500">
<span className="capitalize">{categories[0]}</span> {t("published_by", { author })}

View File

@ -32,11 +32,6 @@ export default function AppCard(props: AppCardProps) {
</div>
<div className="flex items-center">
<h3 className="font-medium">{props.app.name}</h3>
{props.app.isProOnly && user?.plan === "FREE" ? (
<Badge className="ml-2" variant="default">
PRO
</Badge>
) : null}
</div>
{/* TODO: add reviews <div className="flex text-sm text-gray-800">
<span>{props.rating} stars</span> <StarIcon className="ml-1 mt-0.5 h-4 w-4 text-yellow-600" />

View File

@ -114,11 +114,6 @@ const Component = ({
<div className="mb-4 flex items-center">
<img className="min-h-16 min-w-16 h-16 w-16" src={logo} alt={name} />
<h1 className="font-cal ml-4 text-3xl text-gray-900">{name}</h1>
{isProOnly && user?.plan === "FREE" ? (
<Badge className="ml-2" variant="default">
PRO
</Badge>
) : null}
</div>
<h2 className="text-sm font-medium text-gray-600">
<Link href={`categories/${categories[0]}`}>

View File

@ -1,14 +0,0 @@
import { Team, User } from "@prisma/client";
export function isSuccessRedirectAvailable(
eventType: {
users: {
plan: User["plan"];
}[];
} & {
team: Partial<Team> | null;
}
) {
// As Team Event is available in PRO plan only, just check if it's a team event.
return eventType.users[0]?.plan !== "FREE" || eventType.team;
}

View File

@ -4,7 +4,7 @@ import { GetServerSidePropsContext } from "next";
import dynamic from "next/dynamic";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useEffect } from "react";
import { Toaster } from "react-hot-toast";
import { JSONObject } from "superjson/dist/types";
@ -244,7 +244,6 @@ const getEventTypesWithHiddenFromDB = async (userId: number, plan: UserPlan) =>
metadata: true,
...baseEventTypeSelect,
},
take: plan === UserPlan.FREE ? 1 : undefined,
});
};

View File

@ -93,12 +93,9 @@ async function getUserPageProps(context: GetStaticPropsContext) {
if (!user) return { notFound: true };
const eventTypeIds = user.eventTypes.map((e) => e.id);
const eventTypes = await prisma.eventType.findMany({
where: {
slug,
/* Free users can only display their first eventType */
id: user.plan === UserPlan.FREE ? eventTypeIds[0] : undefined,
OR: [{ userId: user.id }, { users: { some: { id: user.id } } }],
},
// Order is important to ensure that given a slug if there are duplicates, we choose the same event type consistently when showing in event-types list UI(in terms of ordering and disabled event types)

View File

@ -36,14 +36,12 @@ import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import Switch from "@calcom/ui/Switch";
import { Tooltip } from "@calcom/ui/Tooltip";
import { UpgradeToProDialog } from "@calcom/ui/UpgradeToProDialog";
import { Form } from "@calcom/ui/form/fields";
import { QueryCell } from "@lib/QueryCell";
import { asStringOrThrow, asStringOrUndefined } from "@lib/asStringOrNull";
import { getSession } from "@lib/auth";
import { HttpError } from "@lib/core/http/error";
import { isSuccessRedirectAvailable } from "@lib/isSuccessRedirectAvailable";
import { slugify } from "@lib/slugify";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@ -132,8 +130,6 @@ const SuccessRedirectEdit = <T extends UseFormReturn<FormValues>>({
formMethods: T;
}) => {
const { t } = useLocale();
const proUpgradeRequired = !isSuccessRedirectAvailable(eventType);
const [modalOpen, setModalOpen] = useState(false);
return (
<>
@ -144,19 +140,12 @@ const SuccessRedirectEdit = <T extends UseFormReturn<FormValues>>({
htmlFor="successRedirectUrl"
className="flex h-full items-center text-sm font-medium text-neutral-700">
{t("redirect_success_booking")}
<span className="ml-1">{proUpgradeRequired && <Badge variant="default">PRO</Badge>}</span>
</label>
</div>
<div className="w-full">
<input
id="successRedirectUrl"
onClick={(e) => {
if (proUpgradeRequired) {
e.preventDefault();
setModalOpen(true);
}
}}
readOnly={proUpgradeRequired}
readOnly={eventType.team !== undefined}
type="url"
className="block w-full rounded-sm border-gray-300 text-sm"
placeholder={t("external_redirect_url")}
@ -164,9 +153,6 @@ const SuccessRedirectEdit = <T extends UseFormReturn<FormValues>>({
{...formMethods.register("successRedirectUrl")}
/>
</div>
<UpgradeToProDialog modalOpen={modalOpen} setModalOpen={setModalOpen}>
{t("redirect_url_upgrade_description")}
</UpgradeToProDialog>
</div>
</>
);

View File

@ -66,10 +66,7 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
return (
<Link href={"/event-types/" + type.id}>
<a
className={classNames(
"flex-grow truncate text-sm ",
type.$disabled && "pointer-events-none cursor-not-allowed opacity-30"
)}
className="flex-grow truncate text-sm"
title={`${type.title} ${type.description ? ` ${type.description}` : ""}`}>
<div>
<span
@ -137,7 +134,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
return {
eventTypeGroups: [],
profiles: [],
viewer: { canAddEvents: false, plan: UserPlan.FREE },
viewer: { canAddEvents: true, plan: UserPlan.PRO },
};
return {
...data,
@ -217,42 +214,28 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
const embedLink = `${group.profile.slug}/${type.slug}`;
const calLink = `${CAL_URL}/${embedLink}`;
return (
<li
key={type.id}
className={classNames(type.$disabled && "select-none")}
data-disabled={type.$disabled ? 1 : 0}>
<div
className={classNames(
"flex items-center justify-between hover:bg-neutral-50 ",
type.$disabled && "hover:bg-white"
)}>
<div
className={classNames(
"group flex w-full items-center justify-between px-4 py-4 pr-0 hover:bg-neutral-50 sm:px-6",
type.$disabled && "hover:bg-white"
)}>
{types.length > 1 && !type.$disabled && (
<>
<button
className="invisible absolute left-[3px] -mt-4 mb-4 -ml-4 hidden h-7 w-7 scale-0 items-center justify-center rounded-full border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[34px]"
onClick={() => moveEventType(index, -1)}>
<Icon.FiArrowUp className="h-5 w-5" />
</button>
<li key={type.id}>
<div className="flex items-center justify-between hover:bg-neutral-50">
<div className="group flex w-full items-center justify-between px-4 py-4 pr-0 hover:bg-neutral-50 sm:px-6">
<button
className="invisible absolute left-[3px] -mt-4 mb-4 -ml-4 hidden h-7 w-7 scale-0 items-center justify-center rounded-full border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[34px]"
onClick={() => moveEventType(index, -1)}>
<Icon.FiArrowUp className="h-5 w-5" />
</button>
<button
className="invisible absolute left-[3px] mt-8 -ml-4 hidden h-7 w-7 scale-0 items-center justify-center rounded-full border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[34px]"
onClick={() => moveEventType(index, 1)}>
<Icon.FiArrowDown className="h-5 w-5" />
</button>
<button
className="invisible absolute left-[3px] mt-8 -ml-4 hidden h-7 w-7 scale-0 items-center justify-center rounded-full border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[34px]"
onClick={() => moveEventType(index, 1)}>
<Icon.FiArrowDown className="h-5 w-5" />
</button>
</>
)}
<MemoizedItem type={type} group={group} readOnly={readOnly} />
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 sm:flex">
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
{type.users?.length > 1 && (
<AvatarGroup
border="border-2 border-white"
className={classNames("relative top-1 right-3", type.$disabled && " opacity-30")}
className="relative top-1 right-3"
size={8}
truncateAfter={4}
items={type.users.map((organizer) => ({
@ -262,11 +245,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
}))}
/>
)}
<div
className={classNames(
"flex justify-between space-x-2 rtl:space-x-reverse ",
type.$disabled && "pointer-events-none cursor-not-allowed"
)}>
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
<Tooltip side="top" content={t("preview") as string}>
<Button
target="_blank"
@ -274,7 +253,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
type="button"
size="icon"
color="minimal"
className={classNames(!type.$disabled && "group-hover:text-black")}
className="group-hover:text-black"
StartIcon={Icon.FiExternalLink}
href={calLink}
/>
@ -285,7 +264,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
type="button"
size="icon"
color="minimal"
className={classNames(type.$disabled ? " opacity-30" : "group-hover:text-black")}
className="group-hover:text-black"
StartIcon={Icon.FiLink}
onClick={() => {
showToast(t("link_copied"), "success");
@ -300,7 +279,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
type="button"
size="icon"
color="minimal"
className={classNames(type.$disabled ? " opacity-30" : "group-hover:text-black")}
className="group-hover:text-black"
StartIcon={Icon.FiMoreHorizontal}
/>
</DropdownMenuTrigger>
@ -310,7 +289,6 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<Button
type="button"
size="sm"
disabled={type.$disabled}
color="minimal"
StartIcon={Icon.FiEdit2}
className="w-full">
@ -323,7 +301,6 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
type="button"
color="minimal"
size="sm"
disabled={type.$disabled}
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy}
onClick={() => openModal(group, type)}>
@ -337,10 +314,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
type="button"
as={Button}
StartIcon={Icon.FiCode}
className={classNames(
"w-full rounded-none",
type.$disabled && " pointer-events-none cursor-not-allowed opacity-30"
)}
className="w-full rounded-none"
embedUrl={encodeURIComponent(embedLink)}>
{t("embed")}
</EmbedButton>
@ -370,13 +344,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<div className="mr-5 flex flex-shrink-0 sm:hidden">
<Dropdown>
<DropdownMenuTrigger asChild data-testid={"event-type-options-" + type.id}>
<Button
type="button"
size="icon"
color="minimal"
className={classNames(type.$disabled && " opacity-30")}
StartIcon={Icon.FiMoreHorizontal}
/>
<Button type="button" size="icon" color="minimal" StartIcon={Icon.FiMoreHorizontal} />
</DropdownMenuTrigger>
<DropdownMenuContent portalled>
<DropdownMenuItem className="outline-none">
@ -555,9 +523,7 @@ const CTA = () => {
if (!query.data) return null;
return (
<CreateEventTypeButton canAddEvents={query.data.viewer.canAddEvents} options={query.data.profiles} />
);
return <CreateEventTypeButton canAddEvents={true} options={query.data.profiles} />;
};
const WithQuery = withQuery(["viewer.eventTypes"]);
@ -582,23 +548,6 @@ const EventTypesPage = () => {
customLoader={<SkeletonLoader />}
success={({ data }) => (
<>
{data.viewer.plan === "FREE" && !data.viewer.canAddEvents && (
<Alert
severity="warning"
title={<>{t("plan_upgrade")}</>}
message={
<Trans i18nKey="plan_upgrade_instructions">
You can
<LinkText href="/api/upgrade" classNameChildren="underline">
upgrade here
</LinkText>
.
</Trans>
}
className="mb-4"
/>
)}
<NoCalendarConnectedAlert />
{data.eventTypeGroups.map((group, index) => (

View File

@ -29,7 +29,6 @@ import { Dialog, DialogTrigger } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { UpgradeToProDialog } from "@calcom/ui/UpgradeToProDialog";
import { Form, PasswordField } from "@calcom/ui/form/fields";
import { Label } from "@calcom/ui/form/fields";
import { withQuery } from "@lib/QueryCell";
import { asStringOrNull, asStringOrUndefined } from "@lib/asStringOrNull";
@ -508,7 +507,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
<div className="text-sm ltr:ml-3 rtl:mr-3">
<label htmlFor="hide-branding" className="font-medium text-gray-700">
{t("disable_cal_branding")}{" "}
{user.plan !== "PRO" && <Badge variant="default">PRO</Badge>}
{user.plan !== "PRO" && <Badge variant="default">TEAM</Badge>}
</label>
<p className="text-gray-500">{t("disable_cal_branding_description")}</p>
</div>

View File

@ -36,7 +36,6 @@ import { EmailInput } from "@calcom/ui/form/fields";
import { asStringOrThrow } from "@lib/asStringOrNull";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import { isSuccessRedirectAvailable } from "@lib/isSuccessRedirectAvailable";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import CancelBooking from "@components/booking/CancelBooking";
@ -276,9 +275,7 @@ export default function Success(props: SuccessProps) {
<CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} />
<main className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "" : "max-w-3xl")}>
<div className={classNames("overflow-y-auto", isEmbed ? "" : "z-50 ")}>
{isSuccessRedirectAvailable(eventType) && eventType.successRedirectUrl ? (
<RedirectionToast url={eventType.successRedirectUrl} />
) : null}{" "}
{eventType.successRedirectUrl ? <RedirectionToast url={eventType.successRedirectUrl} /> : null}{" "}
<div
className={classNames(
shouldAlignCentrally ? "text-center" : "",

View File

@ -1,5 +1,4 @@
import { UserPlan } from "@prisma/client";
import { Trans } from "next-i18next";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
@ -9,7 +8,6 @@ import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Alert } from "@calcom/ui/Alert";
import { Badge, Button, ButtonGroup, Dialog, EmptyScreen, showToast, Switch, Tooltip } from "@calcom/ui/v2";
import ConfirmationDialogContent from "@calcom/ui/v2/core/ConfirmationDialogContent";
import Dropdown, {
@ -23,7 +21,6 @@ import Shell from "@calcom/ui/v2/core/Shell";
import CreateEventTypeButton from "@calcom/ui/v2/modules/event-types/CreateEventType";
import { withQuery } from "@lib/QueryCell";
import classNames from "@lib/classNames";
import { HttpError } from "@lib/core/http/error";
import { EmbedButton, EmbedDialog } from "@components/Embed";
@ -57,10 +54,7 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
return (
<Link href={`/event-types/${type.id}`}>
<a
className={classNames(
"flex-grow truncate text-sm ",
type.$disabled && "pointer-events-none cursor-not-allowed opacity-30"
)}
className="flex-grow truncate text-sm"
title={`${type.title} ${type.description ? ` ${type.description}` : ""}`}>
<div>
<span
@ -130,7 +124,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
return {
eventTypeGroups: [],
profiles: [],
viewer: { canAddEvents: false, plan: UserPlan.FREE },
viewer: { canAddEvents: true, plan: UserPlan.PRO },
};
return {
...data,
@ -212,38 +206,23 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
const embedLink = `${group.profile.slug}/${type.slug}`;
const calLink = `${CAL_URL}/${embedLink}`;
return (
<li
key={type.id}
className={classNames(type.$disabled && "select-none")}
data-disabled={type.$disabled ? 1 : 0}>
<div
className={classNames(
"flex items-center justify-between hover:bg-neutral-50",
type.$disabled && "hover:bg-white"
)}>
<div
className={classNames(
"group flex w-full items-center justify-between px-4 py-4 pr-0 sm:px-6",
type.$disabled && "hover:bg-white"
)}>
{types.length > 1 && !type.$disabled && (
<>
{!(firstItem && firstItem.id === type.id) && (
<button
className="invisible absolute left-[5px] -mt-4 mb-4 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, -1)}>
<Icon.FiArrowUp className="h-5 w-5" />
</button>
)}
<li key={type.id}>
<div className="flex items-center justify-between hover:bg-neutral-50">
<div className="group flex w-full items-center justify-between px-4 py-4 pr-0 sm:px-6">
{!(firstItem && firstItem.id === type.id) && (
<button
className="invisible absolute left-[5px] -mt-4 mb-4 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, -1)}>
<Icon.FiArrowUp className="h-5 w-5" />
</button>
)}
{!(lastItem && lastItem.id === type.id) && (
<button
className="invisible absolute left-[5px] mt-8 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, 1)}>
<Icon.FiArrowDown className="h-5 w-5" />
</button>
)}
</>
{!(lastItem && lastItem.id === type.id) && (
<button
className="invisible absolute left-[5px] mt-8 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, 1)}>
<Icon.FiArrowDown className="h-5 w-5" />
</button>
)}
<MemoizedItem type={type} group={group} readOnly={readOnly} />
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 sm:flex">
@ -251,7 +230,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
{type.users?.length > 1 && (
<AvatarGroup
border="border-2 border-white"
className={classNames("relative top-1 right-3", type.$disabled && " opacity-30")}
className="relative top-1 right-3"
size={8}
truncateAfter={4}
items={type.users.map((organizer) => ({
@ -261,11 +240,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
}))}
/>
)}
<div
className={classNames(
"flex items-center justify-between space-x-2 rtl:space-x-reverse ",
type.$disabled && "pointer-events-none cursor-not-allowed"
)}>
<div className="flex items-center justify-between space-x-2 rtl:space-x-reverse">
{type.hidden && (
<Badge variant="gray" size="lg">
{t("hidden")}
@ -291,7 +266,6 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
size="icon"
href={calLink}
StartIcon={Icon.FiExternalLink}
disabled={type.$disabled}
combined
/>
</Tooltip>
@ -301,7 +275,6 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="secondary"
size="icon"
StartIcon={Icon.FiLink}
disabled={type.$disabled}
onClick={() => {
showToast(t("link_copied"), "success");
navigator.clipboard.writeText(calLink);
@ -324,7 +297,6 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<DropdownItem
type="button"
href={"/event-types/" + type.id}
disabled={type.$disabled}
StartIcon={Icon.FiEdit2}>
{t("edit") as string}
</DropdownItem>
@ -333,7 +305,6 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<DropdownItem
type="button"
data-testid={"event-type-duplicate-" + type.id}
disabled={type.$disabled}
StartIcon={Icon.FiCopy}
onClick={() => openModal(group, type)}>
{t("duplicate") as string}
@ -344,10 +315,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
as={DropdownItem}
type="button"
StartIcon={Icon.FiCode}
className={classNames(
"w-full rounded-none",
type.$disabled && " pointer-events-none cursor-not-allowed opacity-30"
)}
className="w-full rounded-none"
embedUrl={encodeURIComponent(embedLink)}>
{t("embed")}
</EmbedButton>
@ -362,7 +330,6 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
setDeleteDialogTypeId(type.id);
}}
StartIcon={Icon.FiTrash}
disabled={type.$disabled}
className="w-full rounded-none">
{t("delete") as string}
</DropdownItem>
@ -378,13 +345,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<div className="mr-5 flex flex-shrink-0 sm:hidden">
<Dropdown>
<DropdownMenuTrigger asChild data-testid={"event-type-options-" + type.id}>
<Button
type="button"
size="icon"
color="secondary"
className={classNames(type.$disabled && " opacity-30")}
StartIcon={Icon.FiMoreHorizontal}
/>
<Button type="button" size="icon" color="secondary" StartIcon={Icon.FiMoreHorizontal} />
</DropdownMenuTrigger>
<DropdownMenuContent portalled>
<DropdownMenuItem className="outline-none">
@ -553,9 +514,7 @@ const CTA = () => {
if (!query.data) return null;
return (
<CreateEventTypeButton canAddEvents={query.data.viewer.canAddEvents} options={query.data.profiles} />
);
return <CreateEventTypeButton canAddEvents={true} options={query.data.profiles} />;
};
const WithQuery = withQuery(["viewer.eventTypes"]);
@ -576,22 +535,6 @@ const EventTypesPage = () => {
customLoader={<SkeletonLoader />}
success={({ data }) => (
<>
{data.viewer.plan === "FREE" && !data.viewer.canAddEvents && (
<Alert
severity="warning"
title={<>{t("plan_upgrade")}</>}
message={
<Trans i18nKey="plan_upgrade_instructions">
You can
<a href="/api/upgrade" className="underline">
upgrade here
</a>
.
</Trans>
}
className="mb-4"
/>
)}
{data.eventTypeGroups.map((group, index) => (
<Fragment key={group.profile.slug}>
{/* hide list heading when there is only one (current user) */}

View File

@ -34,8 +34,6 @@ import { Icon } from "@calcom/ui/Icon";
import { EmailInput } from "@calcom/ui/form/fields";
import { asStringOrThrow } from "@lib/asStringOrNull";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import { isSuccessRedirectAvailable } from "@lib/isSuccessRedirectAvailable";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import CancelBooking from "@components/booking/CancelBooking";
@ -275,9 +273,7 @@ export default function Success(props: SuccessProps) {
<CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} />
<main className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "" : "max-w-3xl")}>
<div className={classNames("overflow-y-auto", isEmbed ? "" : "z-50 ")}>
{isSuccessRedirectAvailable(eventType) && eventType.successRedirectUrl ? (
<RedirectionToast url={eventType.successRedirectUrl} />
) : null}{" "}
{eventType.successRedirectUrl ? <RedirectionToast url={eventType.successRedirectUrl} /> : null}{" "}
<div
className={classNames(
shouldAlignCentrally ? "text-center" : "",
@ -868,7 +864,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
return {
props: {
hideBranding: eventType.team ? eventType.team.hideBranding : isBrandingHidden(eventType.users[0]),
hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding,
profile,
eventType,
recurringBookings: recurringBookings ? recurringBookings.map((obj) => obj.startTime.toString()) : null,

View File

@ -19,12 +19,6 @@ test.describe("free user", () => {
await users.deleteAll();
});
test("only one visible event", async ({ page, users }) => {
const [free] = users.get();
await expect(page.locator(`[href="/${free.username}/${free.eventTypes[0].slug}"]`)).toBeVisible();
await expect(page.locator(`[href="/${free.username}/${free.eventTypes[1].slug}"]`)).not.toBeVisible();
});
test("cannot book same slot multiple times", async ({ page }) => {
// Click first event type
await page.click('[data-testid="event-type-link"]');
@ -56,16 +50,6 @@ test.describe("free user", () => {
// check for error message
await expect(page.locator("[data-testid=booking-fail]")).toBeVisible();
});
test("Second event type is not bookable", async ({ page, users }) => {
const [free] = users.get();
// Not available in listing
await expect(page.locator(`[href="/${free.username}/${free.eventTypes[1].slug}"]`)).toHaveCount(0);
await page.goto(`/${free.username}/${free.eventTypes[1].slug}`);
// Not available on a direct visit to event type page
await expect(page.locator('[data-testid="404-page"]')).toBeVisible();
});
});
test.describe("pro user", () => {

View File

@ -23,10 +23,6 @@ test.describe("Event Types tests", () => {
const $eventTypes = page.locator("[data-testid=event-types] > *");
const count = await $eventTypes.count();
expect(count).toBeGreaterThanOrEqual(2);
for (let i = 0; i < count; i++) {
expect(await $eventTypes.nth(i).getAttribute("data-disabled")).toBe("0");
}
});
test("can add new event type", async ({ page }) => {
@ -145,15 +141,6 @@ test.describe("Event Types tests", () => {
const $eventTypes = page.locator("[data-testid=event-types] > *");
const count = await $eventTypes.count();
expect(count).toBeGreaterThanOrEqual(2);
const $first = $eventTypes.first();
const $last = $eventTypes.last()!;
expect(await $first.getAttribute("data-disabled")).toBe("0");
expect(await $last.getAttribute("data-disabled")).toBe("1");
});
test("can not add new event type", async ({ page }) => {
await expect(page.locator("[data-testid=new-event-type]")).toBeDisabled();
});
test("edit first event", async ({ page }) => {

View File

@ -29,8 +29,6 @@ test.describe("user can login & logout succesfully", async () => {
const planLocator = shellLocator.locator(`[data-testid=plan-trial]`);
await expect(planLocator).toBeVisible();
await expect(planLocator).toHaveText("-TRIAL");
await expect(page.locator(`[data-testid=trial-banner]`)).toBeVisible();
});
//

View File

@ -1051,8 +1051,8 @@
"navigate": "Navigate",
"open": "Open",
"close": "Close",
"pro_feature_teams": "This is a Pro feature. Upgrade to Pro to see your team's availability.",
"pro_feature_workflows": "This is a Pro feature. Upgrade to Pro to automate your event notifications and reminders with Workflows.",
"team_feature_teams": "This is a Team feature. Upgrade to Team to see your team's availability.",
"team_feature_workflows": "This is a Team feature. Upgrade to Team to automate your event notifications and reminders with Workflows.",
"show_eventtype_on_profile": "Show on Profile",
"embed": "Embed",
"new_username": "New username",

@ -1 +1 @@
Subproject commit 95f4578737d213aa2ab4831f2b9cd520324a53e6
Subproject commit b9d73fd609ce072259a77f9ea460a4ec08d9ca98

View File

@ -1,12 +1,10 @@
import { useRouter } from "next/router";
import { useState, useEffect, useRef } from "react";
import { useEffect, useRef } from "react";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { App } from "@calcom/types/App";
import { UpgradeToProDialog } from "@calcom/ui/UpgradeToProDialog";
import { InstallAppButtonMap } from "./apps.browser.generated";
import { InstallAppButtonProps } from "./types";
@ -30,8 +28,6 @@ export const InstallAppButton = (
} & InstallAppButtonProps
) => {
const { isLoading, data: user } = trpc.useQuery(["viewer.me"]);
const { t } = useLocale();
const [modalOpen, setModalOpen] = useState(false);
const router = useRouter();
const proProtectionElementRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
@ -49,11 +45,6 @@ export const InstallAppButton = (
e.stopPropagation();
return;
}
if (user.plan === "FREE" && props.isProOnly) {
setModalOpen(true);
e.stopPropagation();
return;
}
},
true
);
@ -66,9 +57,6 @@ export const InstallAppButton = (
return (
<div ref={proProtectionElementRef}>
<InstallAppButtonWithoutPlanCheck {...props} />
<UpgradeToProDialog modalOpen={modalOpen} setModalOpen={setModalOpen}>
{t("app_upgrade_description")}
</UpgradeToProDialog>
</div>
);
};

View File

@ -0,0 +1,2 @@
UPDATE "users" SET "plan" = 'FREE' WHERE "plan" = 'PRO' OR "plan" = 'TRIAL';
ALTER TABLE "users" ALTER COLUMN "plan" SET DEFAULT 'FREE';

View File

@ -163,7 +163,7 @@ model User {
identityProviderId String?
availability Availability[]
invitedTo Int?
plan UserPlan @default(TRIAL)
plan UserPlan @default(FREE)
webhooks Webhook[]
brandColor String @default("#292929")
darkBrandColor String @default("#fafafa")

View File

@ -1,7 +1,6 @@
import { AppCategories, BookingStatus, IdentityProvider, MembershipRole, Prisma } from "@prisma/client";
import _ from "lodash";
import { authenticator } from "otplib";
import { JSONObject } from "superjson/dist/types";
import { z } from "zod";
import app_RoutingForms from "@calcom/app-store/ee/routing_forms/trpc-router";
@ -383,19 +382,16 @@ const loggedInViewerRouter = createProtectedRouter()
membershipCount: number;
readOnly: boolean;
};
eventTypes: (typeof user.eventTypes[number] & { $disabled?: boolean })[];
eventTypes: typeof user.eventTypes[number][];
};
let eventTypeGroups: EventTypeGroup[] = [];
const eventTypesHashMap = user.eventTypes.concat(typesRaw).reduce((hashMap, newItem, currentIndex) => {
const oldItem = hashMap[newItem.id] || {
$disabled: user.plan === "FREE" && currentIndex > 0,
};
const eventTypesHashMap = user.eventTypes.concat(typesRaw).reduce((hashMap, newItem) => {
const oldItem = hashMap[newItem.id];
hashMap[newItem.id] = { ...oldItem, ...newItem };
return hashMap;
}, {} as Record<number, EventTypeGroup["eventTypes"][number]>);
const mergedEventTypes = Object.values(eventTypesHashMap).map((eventType) => eventType);
eventTypeGroups.push({
teamId: null,
profile: {
@ -426,11 +422,8 @@ const loggedInViewerRouter = createProtectedRouter()
}))
);
const canAddEvents = user.plan !== "FREE" || eventTypeGroups[0].eventTypes.length < 1;
return {
viewer: {
canAddEvents,
plan: user.plan,
},
// don't display event teams without event types,

View File

@ -82,7 +82,7 @@ export const viewerTeamsRouter = createProtectedRouter()
}),
async resolve({ ctx, input }) {
if (ctx.user.plan === "FREE") {
throw new TRPCError({ code: "UNAUTHORIZED", message: "You are not a pro user." });
throw new TRPCError({ code: "UNAUTHORIZED", message: "You need a team plan." });
}
const slug = slugify(input.name);

View File

@ -8,7 +8,6 @@ import { Toaster } from "react-hot-toast";
import dayjs from "@calcom/dayjs";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import TrialBanner from "@calcom/features/ee/common/components/TrialBanner";
import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner";
import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem";
import UserV2OptInBanner from "@calcom/features/users/components/UserV2OptInBanner";
@ -655,8 +654,6 @@ function SideBarContainer() {
}
function SideBar() {
const { isLocaleReady } = useLocale();
return (
<aside className="desktop-transparent hidden w-14 flex-col border-r border-gray-100 bg-gray-50 md:flex lg:w-56 lg:flex-shrink-0 lg:px-4">
<div className="flex h-0 flex-1 flex-col overflow-y-auto pt-3 pb-4 lg:pt-5">
@ -702,7 +699,6 @@ function SideBar() {
<UserV2OptInBanner />
</div>
{!isLocaleReady ? null : <TrialBanner />}
<div data-testid="user-dropdown-trigger">
<span className="hidden lg:inline">
<UserDropdown />

View File

@ -16,7 +16,6 @@ interface AppCardProps {
export default function AppCard({ app, credentials }: AppCardProps) {
const { t } = useLocale();
const { data: user } = trpc.useQuery(["viewer.me"]);
const mutation = useAddAppMutation(null, {
onSuccess: () => {
@ -116,12 +115,6 @@ export default function AppCard({ app, credentials }: AppCardProps) {
{t("default")}
</span>
)}
{app.isProOnly && user?.plan === "FREE" && (
<span className="flex items-center gap-1 rounded-md bg-orange-100 px-2 py-1 text-sm font-normal text-orange-800">
<Icon.FiStar className="h-4 w-4 text-orange-800" />
{t("pro")}
</span>
)}
</div>
</div>
);