Deprecates user plan (#5942)

* Remove isMissingSeat

* Removes user plan

* Deprecates User Plan

* Updates website

* Update eventTypes.tsx
This commit is contained in:
Omar López 2022-12-08 16:20:24 -07:00 committed by GitHub
parent 5e86881e15
commit e832015f26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 45 additions and 341 deletions

View File

@ -29,10 +29,7 @@ import { trpc } from "@calcom/trpc/react";
import { Icon, DatePicker } from "@calcom/ui";
import { timeZone as localStorageTimeZone } from "@lib/clock";
// import { timeZone } from "@lib/clock";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import Gates, { Gate, GateState } from "@components/Gates";
import AvailableTimes from "@components/booking/AvailableTimes";
@ -280,9 +277,6 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
setTimeZone(localStorageTimeZone() || dayjs.tz.guess());
}, []);
// TODO: Improve this;
useExposePlanGlobally(eventType.users.length === 1 ? eventType.users[0].plan : "PRO");
const [recurringEventCount, setRecurringEventCount] = useState(eventType.recurringEvent?.count);
const telemetry = useTelemetry();

View File

@ -1,12 +0,0 @@
import { UserPlan } from "@prisma/client";
/**
* TODO: It should be exposed at a single place.
*/
export function useExposePlanGlobally(plan: UserPlan) {
// Don't wait for component to mount. Do it ASAP. Delaying it would delay UI Configuration.
if (typeof window !== "undefined") {
// This variable is used by embed-iframe to determine if we should allow UI configuration
window.CalComPlan = plan;
}
}

View File

@ -28,7 +28,6 @@ import { baseEventTypeSelect } from "@calcom/prisma/selects";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { BadgeCheckIcon, EventTypeDescriptionLazy as EventTypeDescription, Icon } from "@calcom/ui";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
@ -92,7 +91,6 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const query = { ...router.query };
delete query.user; // So it doesn't display in the Link (and make tests fail)
useExposePlanGlobally("PRO");
const nameOrUsername = user.name || user.username || "";
const telemetry = useTelemetry();
@ -272,7 +270,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
darkBrandColor: true,
avatar: true,
theme: true,
plan: true,
away: true,
verified: true,
allowDynamicBooking: true,

View File

@ -72,7 +72,6 @@ async function getUserPageProps(context: GetStaticPropsContext) {
id: true,
username: true,
away: true,
plan: true,
name: true,
hideBranding: true,
timeZone: true,
@ -130,7 +129,7 @@ async function getUserPageProps(context: GetStaticPropsContext) {
if (!user || !user.eventTypes.length) return { notFound: true };
const [eventType]: (typeof user.eventTypes[number] & {
users: Pick<User, "name" | "username" | "hideBranding" | "plan" | "timeZone">[];
users: Pick<User, "name" | "username" | "hideBranding" | "timeZone">[];
})[] = [
{
...user.eventTypes[0],
@ -139,7 +138,6 @@ async function getUserPageProps(context: GetStaticPropsContext) {
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
plan: user.plan,
timeZone: user.timeZone,
},
],
@ -226,7 +224,6 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
},
},
theme: true,
plan: true,
},
});
@ -246,7 +243,6 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
plan: user.plan,
timeZone: user.timeZone,
};
}),

View File

@ -46,7 +46,6 @@ async function handler(req: NextApiRequest) {
name: parsedQuery.data.full_name,
emailVerified: new Date(),
locale: "en", // TODO: We should revisit this
plan: "PRO",
identityProvider: IdentityProvider.CAL,
},
});

View File

@ -1,10 +1,5 @@
import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import prisma from "@calcom/prisma";
import { TRIAL_LIMIT_DAYS } from "@lib/config/constants";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const apiKey = req.headers.authorization || req.query.apiKey;
if (process.env.CRON_API_KEY !== apiKey) {
@ -18,35 +13,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
/**
* TODO:
* We should add and extra check for non-paying customers in Stripe so we can
* downgrade them here.
* Remove this endpoint
*/
await prisma.user.updateMany({
data: {
plan: "FREE",
},
where: {
plan: "TRIAL",
OR: [
/**
* If the user doesn't have a trial end date,
* use the default 14 day trial from creation.
*/
{
createdDate: {
lt: dayjs().subtract(TRIAL_LIMIT_DAYS, "day").toDate(),
},
trialEndsAt: null,
},
/** If it does, then honor the trial end date. */
{
trialEndsAt: {
lt: dayjs().toDate(),
},
},
],
},
});
res.json({ ok: true });
}

View File

@ -47,7 +47,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
select: {
username: true,
id: true,
plan: true,
createdDate: true,
},
});
@ -81,7 +80,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
? new Date(lastBooking.booking.createdAt).toLocaleDateString("en-US")
: "No info"
}</li>
<li><b>Plan:</b>&nbsp;${user.plan}</li>
<li><b>Account created:</b>&nbsp;${new Date(user.createdDate).toLocaleDateString("en-US")}</li>
</ul>
`,

View File

@ -858,7 +858,6 @@ const getEventTypesFromDB = async (id: number) => {
name: true,
username: true,
hideBranding: true,
plan: true,
theme: true,
brandColor: true,
darkBrandColor: true,

View File

@ -90,7 +90,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
},
theme: true,
plan: true,
},
});
@ -117,7 +116,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: u.name,
username: u.username,
hideBranding: u.hideBranding,
plan: u.plan,
timeZone: u.timeZone,
})),
});
@ -158,7 +156,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
away: user.away,
isDynamicGroup: false,
profile,
plan: user.plan,
date,
eventType: eventTypeObject,
workingHours,

View File

@ -341,7 +341,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
id: true,
avatar: true,
email: true,
plan: true,
locale: true,
defaultScheduleId: true,
});

View File

@ -164,7 +164,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
weekStart: true,
hideBranding: true,
theme: true,
plan: true,
brandColor: true,
darkBrandColor: true,
metadata: true,

View File

@ -36,7 +36,6 @@ const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
const BillingView = () => {
const { t } = useLocale();
const { data: user } = trpc.viewer.me.useQuery();
const isPro = user?.plan === "PRO";
const [, loadChat] = useChat();
const [showChat, setShowChat] = useState(false);
const router = useRouter();
@ -53,14 +52,9 @@ const BillingView = () => {
<Meta title={t("billing")} description={t("manage_billing_description")} />
<div className="space-y-6 text-sm sm:space-y-8">
<CtaRow
className={classNames(!isPro && "pointer-events-none opacity-30")}
title={t("billing_manage_details_title")}
description={t("billing_manage_details_description")}>
<Button
color={isPro ? "primary" : "secondary"}
href={billingHref}
target="_blank"
EndIcon={Icon.FiExternalLink}>
<Button color="primary" href={billingHref} target="_blank" EndIcon={Icon.FiExternalLink}>
{t("billing_portal")}
</Button>
</CtaRow>

View File

@ -1,4 +1,3 @@
import { UserPlan } from "@prisma/client";
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
@ -14,7 +13,6 @@ import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Avatar, Button, EventTypeDescription, Icon } from "@calcom/ui";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@ -27,7 +25,6 @@ function TeamPage({ team }: TeamPageProps) {
useTheme();
const showMembers = useToggleQuery("members");
const { t } = useLocale();
useExposePlanGlobally("PRO");
const isEmbed = useIsEmbed();
const telemetry = useTelemetry();
const router = useRouter();
@ -137,10 +134,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!team) return { notFound: true } as { notFound: true };
const members = team.members.filter((member) => member.plan !== UserPlan.FREE);
team.members = members ?? [];
team.eventTypes = team.eventTypes.map((type) => ({
...type,
users: type.users.map((user) => ({

View File

@ -1,4 +1,3 @@
import { UserPlan } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { privacyFilteredLocations, LocationObject } from "@calcom/core/location";
@ -60,7 +59,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
username: true,
timeZone: true,
hideBranding: true,
plan: true,
brandColor: true,
darkBrandColor: true,
},
@ -130,7 +128,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
plan: user.plan,
timeZone: user.timeZone,
})),
});
@ -144,8 +141,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return {
props: {
// Team is always pro
plan: "PRO" as UserPlan,
profile: {
name: team.name || team.slug,
slug: team.slug,

View File

@ -13,7 +13,7 @@ test.describe.configure({ mode: "parallel" });
test.describe("free user", () => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create({ plan: "FREE" });
const free = await users.create();
await page.goto(`/${free.username}`);
});
test.afterEach(async ({ users }) => {

View File

@ -1,7 +1,5 @@
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
import stripe from "@calcom/features/ee/payments/server/stripe";
import { WEBAPP_URL } from "@calcom/lib/constants";

View File

@ -10,7 +10,7 @@ import {
test("dynamic booking", async ({ page, users }) => {
const pro = await users.create();
await pro.login();
const free = await users.create({ plan: "FREE" });
const free = await users.create({ username: "free" });
await page.goto(`/${pro.username}+${free.username}`);
await test.step("book an event first day in next month", async () => {

View File

@ -120,37 +120,7 @@ test.describe("Event Types tests", () => {
});
await page.locator("[data-testid=update-eventtype]").click();
const toast = await page.waitForSelector("div[class*='data-testid-toast-success']");
await expect(toast).toBeTruthy();
});
});
test.describe("free user", () => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create({ plan: "FREE" });
await free.login();
await page.goto("/event-types");
// We wait until loading is finished
await page.waitForSelector('[data-testid="event-types"]');
});
test("has at least 2 events where first is enabled", async ({ page }) => {
const $eventTypes = page.locator("[data-testid=event-types] > li a");
const count = await $eventTypes.count();
expect(count).toBeGreaterThanOrEqual(2);
});
test("edit first event", async ({ page }) => {
const $eventTypes = page.locator("[data-testid=event-types] > li a");
const firstEventTypeElement = $eventTypes.first();
await firstEventTypeElement.click();
await page.waitForNavigation({
url: (url) => {
return !!url.pathname.match(/\/event-types\/.+/);
},
});
await page.locator("[data-testid=update-eventtype]").click();
const toast = await page.waitForSelector("div[class*='data-testid-toast-success']");
await expect(toast).toBeTruthy();
expect(toast).toBeTruthy();
});
});
});

View File

@ -1,6 +1,6 @@
import type { Page, WorkerInfo } from "@playwright/test";
import type Prisma from "@prisma/client";
import { Prisma as PrismaType, UserPlan } from "@prisma/client";
import { Prisma as PrismaType } from "@prisma/client";
import { hash } from "bcryptjs";
import dayjs from "@calcom/dayjs";
@ -238,7 +238,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
};
};
type CustomUserOptsKeys = "username" | "password" | "plan" | "completedOnboarding" | "locale" | "name";
type CustomUserOptsKeys = "username" | "password" | "completedOnboarding" | "locale" | "name";
type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & { timeZone?: TimeZoneEnum };
// creates the actual user in the db.
@ -247,13 +247,10 @@ const createUser = async (
opts?: CustomUserOpts | null
): Promise<PrismaType.UserCreateInput> => {
// build a unique name for our user
const uname = `${opts?.username ?? opts?.plan?.toLocaleLowerCase() ?? UserPlan.PRO.toLowerCase()}-${
workerInfo.workerIndex
}-${Date.now()}`;
const uname = `${opts?.username}-${workerInfo.workerIndex}-${Date.now()}`;
return {
username: uname,
name: opts?.name === undefined ? (opts?.plan ?? UserPlan.PRO).toUpperCase() : opts?.name,
plan: opts?.plan ?? UserPlan.PRO,
name: opts?.name,
email: `${uname}@example.com`,
password: await hashPassword(uname),
emailVerified: new Date(),

View File

@ -1,5 +1,4 @@
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { login } from "./fixtures/users";
import { test } from "./lib/fixtures";
@ -13,10 +12,10 @@ test.describe("user can login & logout succesfully", async () => {
test.afterAll(async ({ users }) => {
await users.deleteAll();
});
test("login flow TRAIL user & logout using dashboard", async ({ page, users }) => {
test("login flow user & logout using dashboard", async ({ page, users }) => {
// log in trail user
await test.step("Log in", async () => {
const user = await users.create({ plan: UserPlan.TRIAL });
const user = await users.create();
await user.login();
const shellLocator = page.locator(`[data-testid=dashboard-shell]`);
@ -24,11 +23,6 @@ test.describe("user can login & logout succesfully", async () => {
// expects the home page for an authorized user
await page.goto("/");
await expect(shellLocator).toBeVisible();
// Asserts to read the tested plan
const planLocator = shellLocator.locator(`[data-testid=plan-trial]`);
await expect(planLocator).toBeVisible();
await expect(planLocator).toHaveText("-TRIAL");
});
//
@ -64,27 +58,6 @@ test.describe("Login and logout tests", () => {
await expect(page.locator(`[data-testid=login-form]`)).toBeVisible();
});
// Test login with all plans
const plans = [UserPlan.PRO, UserPlan.FREE];
plans.forEach((plan) => {
test(`Should login with a ${plan} account`, async ({ page, users }) => {
// Create user and login
const user = await users.create({ plan });
await user.login();
const shellLocator = page.locator(`[data-testid=dashboard-shell]`);
// expects the home page for an authorized user
await page.goto("/");
await expect(shellLocator).toBeVisible();
// Asserts to read the tested plan
const planLocator = shellLocator.locator(`[data-testid=plan-${plan.toLowerCase()}]`);
await expect(planLocator).toBeVisible();
await expect(planLocator).toHaveText(`-${plan}`);
});
});
test.describe("Login flow validations", async () => {
test("Should warn when user does not exist", async ({ page }) => {
const alertMessage = (await localize("en"))("no_account_exists");

View File

@ -1,6 +1,5 @@
/* eslint-disable playwright/no-skipped-test */
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { test } from "./lib/fixtures";
@ -9,7 +8,7 @@ test.describe.configure({ mode: "serial" });
test.describe("Onboarding", () => {
test.describe("Onboarding v2", () => {
test("Onboarding Flow", async ({ page, users }) => {
const user = await users.create({ plan: UserPlan.TRIAL, completedOnboarding: false, name: null });
const user = await users.create({ completedOnboarding: false, name: null });
await user.login();
// tests whether the user makes it to /getting-started

View File

@ -1 +1 @@
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","videoCallData":"[redacted/dynamic]","appsStatus":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between Nameless and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Nameless","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","videoCallData":"[redacted/dynamic]","appsStatus":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}

@ -1 +1 @@
Subproject commit 925f81ed92ee54cb1512c2836077e06d68c95123
Subproject commit 30cbf3990bb5baae7fd4fd46c93f6e5bc402f84c

View File

@ -14,8 +14,6 @@ import { AppGetServerSidePropsContext, AppPrisma } from "@calcom/types/AppGetSer
import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Button, showToast } from "@calcom/ui";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import FormInputFields from "../../components/FormInputFields";
import { getSerializableForm } from "../../lib/getSerializableForm";
import { processRoute } from "../../lib/processRoute";
@ -26,7 +24,6 @@ function RoutingForm({ form, profile, ...restProps }: inferSSRProps<typeof getSe
const formFillerIdRef = useRef(uuidv4());
const isEmbed = useIsEmbed(restProps.isEmbed);
useTheme(profile.theme);
useExposePlanGlobally(profile.plan);
// TODO: We might want to prevent spam from a single user by having same formFillerId across pageviews
// But technically, a user can fill form multiple times due to any number of reasons and we currently can't differentiate b/w that.
// - like a network error
@ -183,7 +180,6 @@ export const getServerSideProps = async function getServerSideProps(
theme: true,
brandColor: true,
darkBrandColor: true,
plan: true,
},
},
},
@ -202,7 +198,6 @@ export const getServerSideProps = async function getServerSideProps(
theme: form.user.theme,
brandColor: form.user.brandColor,
darkBrandColor: form.user.darkBrandColor,
plan: form.user.plan,
},
form: getSerializableForm(form),
},

View File

@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const userData = await prisma.user.findFirst({
where: { id: userId },
select: { id: true, plan: true, metadata: true },
select: { id: true, metadata: true },
});
if (!userData) {
res.status(404).json({ message: "Missing user data" });

View File

@ -1,88 +0,0 @@
#!/usr/bin/env ts-node
// To run this script: `yarn downgrade 2>&1 | tee result.log`
import { Prisma, UserPlan } from "@prisma/client";
import dayjs from "@calcom/dayjs";
import { TRIAL_LIMIT_DAYS } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import { getStripeCustomerIdFromUserId } from "./customer";
import stripe from "./server";
/**
* Deprecated or should be updated
*/
export async function downgradeIllegalProUsers() {
const illegalProUsers = await prisma.user.findMany({
where: {
plan: UserPlan.PRO,
},
select: {
id: true,
name: true,
email: true,
username: true,
plan: true,
metadata: true,
},
});
const usersDowngraded: Partial<typeof illegalProUsers[number]>[] = [];
const downgrade = async (user: typeof illegalProUsers[number]) => {
await prisma.user.update({
where: { id: user.id },
data: {
plan: UserPlan.TRIAL,
trialEndsAt: dayjs().add(TRIAL_LIMIT_DAYS, "day").toDate(),
},
});
console.log(`Downgraded: ${user.email}`);
usersDowngraded.push({
id: user.id,
username: user.username,
name: user.name,
email: user.email,
plan: user.plan,
metadata: user.metadata,
});
};
for (const suspectUser of illegalProUsers) {
console.log(`Checking: ${suspectUser.email}`);
const metadata = (suspectUser.metadata as Prisma.JsonObject) ?? {};
// if their pro is already sponsored by a team, do not downgrade
if (metadata.proPaidForByTeamId !== undefined) continue;
const stripeCustomerId = await getStripeCustomerIdFromUserId(suspectUser.id);
const customer = await stripe.customers.retrieve(stripeCustomerId, {
expand: ["subscriptions.data.plan"],
});
if (!customer || customer.deleted) {
await downgrade(suspectUser);
continue;
}
const subscription = customer.subscriptions?.data[0];
if (!subscription) {
await downgrade(suspectUser);
continue;
}
// If they already have a premium username, do not downgrade
// if (suspectUser.username && isPremiumUserName(suspectUser.username)) continue;
await downgrade(suspectUser);
}
return {
usersDowngraded,
usersDowngradedAmount: usersDowngraded.length,
};
}
downgradeIllegalProUsers()
.then(({ usersDowngraded, usersDowngradedAmount }) => {
console.log(`Downgraded ${usersDowngradedAmount} illegal pro users`);
console.table(usersDowngraded);
})
.catch((e) => {
console.error(e);
process.exit(1);
});

View File

@ -780,7 +780,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
await syncServicesUpdateWebUser(
await prisma.user.findFirst({
where: { id: userId },
select: { id: true, email: true, name: true, plan: true, username: true, createdDate: true },
select: { id: true, email: true, name: true, username: true, createdDate: true },
})
);
evt.uid = booking?.uid ?? null;

View File

@ -58,7 +58,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: true,
username: true,
hideBranding: true,
plan: true,
theme: true,
},
},

View File

@ -1,4 +1,4 @@
import { PrismaClient, UserPlan } from "@prisma/client";
import { PrismaClient } from "@prisma/client";
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
import { isTeamAdmin } from "@calcom/lib/server/queries/teams";
@ -59,11 +59,8 @@ export const samlTenantProduct = async (prisma: PrismaClient, email: string) =>
};
};
export const canAccess = async (
user: { id: number; plan: UserPlan; email: string },
teamId: number | null
) => {
const { id: userId, plan, email } = user;
export const canAccess = async (user: { id: number; email: string }, teamId: number | null) => {
const { id: userId, email } = user;
if (!isSAMLLoginEnabled) {
return {
@ -80,13 +77,6 @@ export const canAccess = async (
access: false,
};
}
if (plan != UserPlan.PRO) {
return {
message: "app_upgrade_description",
access: false,
};
}
}
// Self-hosted

View File

@ -108,7 +108,6 @@ export default function MemberListItem(props: Props) {
<div className="mb-1 flex">
<span className="mr-1 text-sm font-bold leading-4">{name}</span>
{props.member.isMissingSeat && <TeamPill color="red" text={t("hidden")} />}
{!props.member.accepted && <TeamPill color="orange" text={t("pending")} />}
{props.member.role && <TeamRole role={props.member.role} />}
</div>

View File

@ -2,23 +2,17 @@ import { useRouter } from "next/router";
import { useMemo, useState } from "react";
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Alert, Avatar, Loader, Shell } from "@calcom/ui";
import LicenseRequired from "../../common/components/LicenseRequired";
import TeamAvailabilityScreen from "../components/TeamAvailabilityScreen";
export function TeamAvailabilityPage() {
const { t } = useLocale();
const router = useRouter();
const [errorMessage, setErrorMessage] = useState("");
const me = useMeQuery();
const isFreeUser = me.data?.plan === "FREE";
const { data: team, isLoading } = trpc.viewer.teams.get.useQuery(
{ teamId: Number(router.query.id) },
{
@ -37,12 +31,11 @@ export function TeamAvailabilityPage() {
return (
<Shell
backPath={!errorMessage ? `/settings/teams/${team?.id}` : undefined}
heading={!isFreeUser && team?.name}
heading={team?.name}
flexChildrenContainer
subtitle={team && !isFreeUser && "Your team's availability at a glance"}
subtitle={team && "Your team's availability at a glance"}
HeadingLeftIcon={
team &&
!isFreeUser && (
team && (
<Avatar
size="sm"
imageSrc={getPlaceholderAvatar(team?.logo, team?.name as string)}
@ -54,11 +47,7 @@ export function TeamAvailabilityPage() {
<LicenseRequired>
{!!errorMessage && <Alert className="-mt-24 border" severity="error" title={errorMessage} />}
{isLoading && <Loader />}
{isFreeUser ? (
<Alert className="-mt-24 border" severity="warning" title={t("pro_feature_teams")} />
) : (
TeamAvailability
)}
{TeamAvailability}
</LicenseRequired>
</Shell>
);

View File

@ -1,4 +1,4 @@
import { PeriodType, Prisma, SchedulingType, UserPlan } from "@prisma/client";
import { PeriodType, Prisma, SchedulingType } from "@prisma/client";
import { DailyLocationType } from "@calcom/app-store/locations";
import { userSelect } from "@calcom/prisma/selects";
@ -15,7 +15,6 @@ type UsernameSlugLinkProps = {
bio?: string | null;
avatar?: string | null;
theme?: string | null;
plan?: UserPlan;
away?: boolean;
verified?: boolean | null;
allowDynamicBooking?: boolean | null;
@ -41,7 +40,6 @@ const user: User = {
name: "John doe",
avatar: "",
destinationCalendar: null,
plan: UserPlan.PRO,
hideBranding: true,
brandColor: "#797979",
darkBrandColor: "#efefef",

View File

@ -1,4 +1,4 @@
import { Prisma, UserPlan } from "@prisma/client";
import { Prisma } from "@prisma/client";
import prisma, { baseEventTypeSelect } from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
@ -12,7 +12,6 @@ export async function getTeamWithMembers(id?: number, slug?: string, userId?: nu
email: true,
name: true,
id: true,
plan: true,
bio: true,
});
const teamSelect = Prisma.validator<Prisma.TeamSelect>()({
@ -62,7 +61,6 @@ export async function getTeamWithMembers(id?: number, slug?: string, userId?: nu
const members = team.members.map((obj) => {
return {
...obj.user,
isMissingSeat: obj.user.plan === UserPlan.FREE,
role: obj.role,
accepted: obj.accepted,
disableImpersonation: obj.disableImpersonation,

View File

@ -1,6 +1,3 @@
// import { DeploymentType } from "@prisma/admin-client";
import { User } from "@prisma/client";
import logger from "@calcom/lib/logger";
import { default as webPrisma } from "@calcom/prisma";
@ -17,7 +14,8 @@ export type TeamInfoType = {
};
export type WebUserInfoType = UserInfo & {
plan: User["plan"];
/** All users are PRO now */
plan?: "PRO";
};
export type ConsoleUserInfoType = UserInfo & {

View File

@ -1,5 +1,5 @@
import { faker } from "@faker-js/faker";
import { Booking, BookingStatus, EventType, Prisma, UserPlan, Webhook } from "@prisma/client";
import { Booking, BookingStatus, EventType, Prisma, Webhook } from "@prisma/client";
import { CalendarEvent, Person, VideoCallData } from "@calcom/types/Calendar";
@ -197,7 +197,6 @@ export const buildUser = <T extends Partial<UserPayload>>(user?: T): UserPayload
locale: "en",
metadata: null,
password: null,
plan: UserPlan.PRO,
role: "USER",
schedules: [],
selectedCalendars: [],

View File

@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the column `plan` on the `users` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "users" DROP COLUMN "plan";
-- DropEnum
DROP TYPE "UserPlan";

View File

@ -103,12 +103,6 @@ model Credential {
invalid Boolean? @default(false)
}
enum UserPlan {
FREE
TRIAL
PRO
}
enum IdentityProvider {
CAL
GOOGLE
@ -170,7 +164,6 @@ model User {
identityProviderId String?
availability Availability[]
invitedTo Int?
plan UserPlan @default(PRO)
webhooks Webhook[]
brandColor String @default("#292929")
darkBrandColor String @default("#fafafa")

View File

@ -1,4 +1,4 @@
import { BookingStatus, MembershipRole, Prisma, UserPermissionRole, UserPlan } from "@prisma/client";
import { BookingStatus, MembershipRole, Prisma, UserPermissionRole } from "@prisma/client";
import { uuid } from "short-uuid";
import dailyMeta from "@calcom/app-store/dailyvideo/_metadata";
@ -16,7 +16,6 @@ async function createUserAndEventType(opts: {
email: string;
password: string;
username: string;
plan: UserPlan;
name: string;
completedOnboarding?: boolean;
timeZone?: string;
@ -176,7 +175,6 @@ async function main() {
password: "delete-me",
username: "delete-me",
name: "delete-me",
plan: "FREE",
},
eventTypes: [],
});
@ -187,7 +185,6 @@ async function main() {
password: "onboarding",
username: "onboarding",
name: "onboarding",
plan: "TRIAL",
completedOnboarding: false,
},
eventTypes: [],
@ -199,7 +196,6 @@ async function main() {
password: "free-first-hidden",
username: "free-first-hidden",
name: "Free First Hidden Example",
plan: "FREE",
},
eventTypes: [
{
@ -221,7 +217,6 @@ async function main() {
name: "Pro Example",
password: "pro",
username: "pro",
plan: "PRO",
},
eventTypes: [
{
@ -445,7 +440,6 @@ async function main() {
password: "trial",
username: "trial",
name: "Trial Example",
plan: "TRIAL",
},
eventTypes: [
{
@ -467,7 +461,6 @@ async function main() {
password: "free",
username: "free",
name: "Free Example",
plan: "FREE",
},
eventTypes: [
{
@ -489,7 +482,6 @@ async function main() {
password: "usa",
username: "usa",
name: "USA Timezone Example",
plan: "FREE",
timeZone: "America/Phoenix",
},
eventTypes: [
@ -507,7 +499,6 @@ async function main() {
password: "teamfree",
username: "teamfree",
name: "Team Free Example",
plan: "FREE",
},
eventTypes: [],
});
@ -518,7 +509,6 @@ async function main() {
password: "teampro",
username: "teampro",
name: "Team Pro Example",
plan: "PRO",
},
eventTypes: [],
});
@ -530,7 +520,6 @@ async function main() {
password: "ADMINadmin2022!",
username: "admin",
name: "Admin Example",
plan: "PRO",
role: "ADMIN",
},
eventTypes: [],
@ -542,7 +531,6 @@ async function main() {
password: "teampro2",
username: "teampro2",
name: "Team Pro Example 2",
plan: "PRO",
},
eventTypes: [],
});
@ -553,7 +541,6 @@ async function main() {
password: "teampro3",
username: "teampro3",
name: "Team Pro Example 3",
plan: "PRO",
},
eventTypes: [],
});
@ -564,7 +551,6 @@ async function main() {
password: "teampro4",
username: "teampro4",
name: "Team Pro Example 4",
plan: "PRO",
},
eventTypes: [],
});

View File

@ -97,7 +97,6 @@ export const availiblityPageEventTypeSelect = Prisma.validator<Prisma.EventTypeS
name: true,
username: true,
hideBranding: true,
plan: true,
timeZone: true,
},
},

View File

@ -25,7 +25,6 @@ export const baseUserSelect = Prisma.validator<Prisma.UserSelect>()({
name: true,
destinationCalendar: true,
locale: true,
plan: true,
hideBranding: true,
theme: true,
brandColor: true,
@ -40,7 +39,6 @@ export const userSelect = Prisma.validator<Prisma.UserArgs>()({
allowDynamicBooking: true,
destinationCalendar: true,
locale: true,
plan: true,
avatar: true,
hideBranding: true,
theme: true,

View File

@ -49,7 +49,6 @@ async function getUserFromSession({
identityProvider: true,
brandColor: true,
darkBrandColor: true,
plan: true,
away: true,
credentials: {
select: {

View File

@ -167,7 +167,6 @@ const loggedInViewerRouter = router({
identityProvider: user.identityProvider,
brandColor: user.brandColor,
darkBrandColor: user.darkBrandColor,
plan: user.plan,
away: user.away,
bio: user.bio,
weekStart: user.weekStart,
@ -647,7 +646,6 @@ const loggedInViewerRouter = router({
email: true,
metadata: true,
name: true,
plan: true,
createdDate: true,
},
});

View File

@ -1,4 +1,4 @@
import { EventTypeCustomInput, MembershipRole, PeriodType, Prisma } from "@prisma/client";
import { MembershipRole, PeriodType, Prisma } from "@prisma/client";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
// REVIEW: From lint error
import _ from "lodash";
@ -10,12 +10,12 @@ import { stripeDataSchema } from "@calcom/app-store/stripepayment/lib/server";
import { validateBookingLimitOrder } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
import { baseEventTypeSelect, baseUserSelect } from "@calcom/prisma";
import { _DestinationCalendarModel, _EventTypeCustomInputModel, _EventTypeModel } from "@calcom/prisma/zod";
import { _DestinationCalendarModel, _EventTypeModel } from "@calcom/prisma/zod";
import {
customInputSchema,
CustomInputSchema,
EventTypeMetaDataSchema,
stringOrNumber,
CustomInputSchema,
} from "@calcom/prisma/zod-utils";
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
@ -193,7 +193,6 @@ export const eventTypesRouter = router({
startTime: true,
endTime: true,
bufferTime: true,
plan: true,
teams: {
where: {
accepted: true,
@ -321,9 +320,6 @@ export const eventTypesRouter = router({
}))
);
return {
viewer: {
plan: user.plan,
},
// don't display event teams without event types,
eventTypeGroups: eventTypeGroups.filter((groupBy) => !!groupBy.eventTypes?.length),
// so we can show a dropdown when the user has teams
@ -425,7 +421,6 @@ export const eventTypesRouter = router({
endTime: true,
bufferTime: true,
avatar: true,
plan: true,
},
});
if (!user) {

View File

@ -1,4 +1,4 @@
import { MembershipRole, Prisma, UserPlan } from "@prisma/client";
import { MembershipRole, Prisma } from "@prisma/client";
import { randomBytes } from "crypto";
import { z } from "zod";
@ -45,10 +45,8 @@ export const viewerTeamsRouter = router({
...team,
membership: {
role: membership?.role as MembershipRole,
isMissingSeat: membership?.plan === UserPlan.FREE,
accepted: membership?.accepted,
},
requiresUpgrade: HOSTED_CAL_FEATURES ? !!team.members.find((m) => m.plan !== UserPlan.PRO) : false,
};
}),
// Returns teams I a member of

View File

@ -725,9 +725,6 @@ function DeploymentInfo() {
className="mx-3 mt-1 mb-2 hidden opacity-50 lg:block">
&copy; {new Date().getFullYear()} {COMPANY_NAME} v.{pkg.version + "-"}
{process.env.NEXT_PUBLIC_WEBSITE_URL === "https://cal.com" ? "h" : "sh"}
<span className="lowercase" data-testid={`plan-${user?.plan.toLowerCase()}`}>
-{user?.plan}
</span>
</small>
);
}