Merge branch 'main' into web3
commit
cb2842c695
|
@ -16,6 +16,7 @@ import React, { ReactNode, useEffect, useState } from "react";
|
|||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import LicenseBanner from "@ee/components/LicenseBanner";
|
||||
import TrialBanner from "@ee/components/TrialBanner";
|
||||
import HelpMenuItemDynamic from "@ee/lib/intercom/HelpMenuItemDynamic";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
@ -242,6 +243,7 @@ export default function Shell(props: {
|
|||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<TrialBanner />
|
||||
<div className="p-2 pt-2 pr-2 m-2 rounded-sm hover:bg-gray-100">
|
||||
<span className="hidden lg:inline">
|
||||
<UserDropdown />
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import dayjs from "dayjs";
|
||||
|
||||
import { TRIAL_LIMIT_DAYS } from "@lib/config/constants";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
import { useMeQuery } from "@components/Shell";
|
||||
import Button from "@components/ui/Button";
|
||||
|
||||
const TrialBanner = () => {
|
||||
const { t } = useLocale();
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
|
||||
if (!user || user.plan !== "TRIAL") return null;
|
||||
|
||||
const trialDaysLeft = dayjs(user.createdDate)
|
||||
.add(TRIAL_LIMIT_DAYS + 1, "day")
|
||||
.diff(dayjs(), "day");
|
||||
|
||||
return (
|
||||
<div
|
||||
className="p-4 m-4 text-sm font-medium text-center text-gray-600 bg-yellow-200 rounded-md"
|
||||
data-testid="trial-banner">
|
||||
<div className="mb-2 text-left">{t("trial_days_left", { days: trialDaysLeft })}</div>
|
||||
<Button
|
||||
href="/api/upgrade"
|
||||
color="minimal"
|
||||
className="justify-center w-full border-2 border-gray-600 hover:bg-yellow-100">
|
||||
{t("upgrade_now")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrialBanner;
|
|
@ -1,4 +1,5 @@
|
|||
import { loadStripe, Stripe } from "@stripe/stripe-js";
|
||||
import { Stripe } from "@stripe/stripe-js";
|
||||
import { loadStripe } from "@stripe/stripe-js/pure";
|
||||
import { stringify } from "querystring";
|
||||
|
||||
import { Maybe } from "@trpc/server";
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
export const BASE_URL = process.env.BASE_URL || `https://${process.env.VERCEL_URL}`;
|
||||
export const WEBSITE_URL = process.env.NEXT_PUBLIC_APP_URL || "https://cal.com";
|
||||
export const IS_PRODUCTION = process.env.NODE_ENV === "production";
|
||||
export const TRIAL_LIMIT_DAYS = 14;
|
||||
|
|
|
@ -7,4 +7,5 @@ module.exports = {
|
|||
locales: ["en", "fr", "it", "ru", "es", "de", "pt", "ro", "nl", "pt-BR", "es-419", "ko", "ja"],
|
||||
},
|
||||
localePath: path.resolve("./public/static/locales"),
|
||||
reloadOnPrerender: process.env.NODE_ENV !== "production",
|
||||
};
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import dayjs from "dayjs";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { TRIAL_LIMIT_DAYS } from "@lib/config/constants";
|
||||
import prisma from "@lib/prisma";
|
||||
|
||||
const TRIAL_LIMIT_DAYS = 14;
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const apiKey = req.headers.authorization || req.query.apiKey;
|
||||
if (process.env.CRON_API_KEY !== apiKey) {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { getSession } from "@lib/auth";
|
||||
import { WEBSITE_URL } from "@lib/config/constants";
|
||||
import { HttpError as HttpCode } from "@lib/core/http/error";
|
||||
import prisma from "@lib/prisma";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getSession({ req });
|
||||
if (!session?.user?.id) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
if (req.method !== "GET") {
|
||||
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
rejectOnNotFound: true,
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
select: {
|
||||
email: true,
|
||||
metadata: true,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(`${WEBSITE_URL}/api/upgrade`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
stripeCustomerId: (user.metadata as Prisma.JsonObject)?.stripeCustomerId,
|
||||
email: user.email,
|
||||
fromApp: true,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
res.redirect(303, data.url);
|
||||
} catch (error) {
|
||||
console.error(`error`, error);
|
||||
res.redirect(303, req.headers.origin || "/");
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import { useRouter } from "next/router";
|
|||
import { useState } from "react";
|
||||
|
||||
import { ErrorCode, getSession } from "@lib/auth";
|
||||
import { WEBSITE_URL } from "@lib/config/constants";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
|
@ -176,7 +177,7 @@ export default function Login({ csrfToken }: inferSSRProps<typeof getServerSideP
|
|||
</div>
|
||||
<div className="mt-4 text-sm text-center text-neutral-600">
|
||||
{t("dont_have_an_account")} {/* replace this with your account creation flow */}
|
||||
<a href="https://cal.com/signup" className="font-medium text-neutral-900">
|
||||
<a href={`${WEBSITE_URL}/signup`} className="font-medium text-neutral-900">
|
||||
{t("create_an_account")}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -26,7 +26,7 @@ async function globalSetup(/* config: FullConfig */) {
|
|||
await loginAsUser("onboarding", browser);
|
||||
// await loginAsUser("free-first-hidden", browser);
|
||||
await loginAsUser("pro", browser);
|
||||
// await loginAsUser("trial", browser);
|
||||
await loginAsUser("trial", browser);
|
||||
await loginAsUser("free", browser);
|
||||
// await loginAsUser("usa", browser);
|
||||
// await loginAsUser("teamfree", browser);
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
// Using logged in state from globalSteup
|
||||
test.use({ storageState: "playwright/artifacts/trialStorageState.json" });
|
||||
|
||||
test("Trial banner should be visible to TRIAL users", async ({ page }) => {
|
||||
// Try to go homepage
|
||||
await page.goto("/");
|
||||
// It should redirect you to the event-types page
|
||||
await page.waitForSelector("[data-testid=event-types]");
|
||||
|
||||
await expect(page.locator(`[data-testid=trial-banner]`)).toBeVisible();
|
||||
});
|
|
@ -1,4 +1,8 @@
|
|||
{
|
||||
"trial_days_left": "You have $t(day, {\"count\": {{days}} }) left on your PRO trial",
|
||||
"day": "{{count}} day",
|
||||
"day_plural": "{{count}} days",
|
||||
"upgrade_now": "Upgrade now",
|
||||
"accept_invitation": "Accept Invitation",
|
||||
"calcom_explained": "Cal.com is the open source Calendly alternative putting you in control of your own data, workflow and appearance.",
|
||||
"have_any_questions": "Have questions? We're here to help.",
|
||||
|
|
Loading…
Reference in New Issue