Latest tweaks (#4482)
This commit is contained in:
parent
0d9d182b56
commit
7a95e714c2
|
@ -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 })}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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]}`}>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" : "",
|
||||
|
|
|
@ -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) */}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
//
|
||||
|
|
|
@ -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
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
UPDATE "users" SET "plan" = 'FREE' WHERE "plan" = 'PRO' OR "plan" = 'TRIAL';
|
||||
ALTER TABLE "users" ALTER COLUMN "plan" SET DEFAULT 'FREE';
|
|
@ -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")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue
Block a user