From ab78bb38024b5c4ca49d6cead1d6825a2543ebc7 Mon Sep 17 00:00:00 2001 From: Alex Johansson Date: Mon, 13 Sep 2021 11:48:55 +0200 Subject: [PATCH] move calendso branding into pro (#629) * badge * mv branding to paid plan * upgrade ts * hideBranding check * user.plan * lint fixes * `isBrandingHidden` helper * hide pro for non-pros --- components/Modal.tsx | 28 ++++++++-- components/ui/Badge.tsx | 25 +++++++++ lib/isBrandingHidden.tsx | 5 ++ package.json | 2 +- pages/[user]/[type].tsx | 3 +- pages/settings/profile.tsx | 101 ++++++++++++++++++++++++++++--------- pages/success.tsx | 68 +++++++++++++++++-------- yarn.lock | 8 +-- 8 files changed, 185 insertions(+), 55 deletions(-) create mode 100644 components/ui/Badge.tsx create mode 100644 lib/isBrandingHidden.tsx diff --git a/components/Modal.tsx b/components/Modal.tsx index e03b1d1277..df85ec17ee 100644 --- a/components/Modal.tsx +++ b/components/Modal.tsx @@ -1,8 +1,16 @@ -import { Fragment } from "react"; +import { Fragment, ReactNode } from "react"; import { Dialog, Transition } from "@headlessui/react"; -import { CheckIcon } from "@heroicons/react/outline"; +import { CheckIcon, InformationCircleIcon } from "@heroicons/react/outline"; +import classNames from "@lib/classNames"; -export default function Modal(props) { +export default function Modal(props: { + heading: ReactNode; + description: ReactNode; + handleClose: () => void; + open: boolean; + variant?: "success" | "warning"; +}) { + const { variant = "success" } = props; return (
-
-
- {!props.user.hideBranding && } + {!isBrandingHidden(props.user) && }
)} diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx index 78f0e2415f..79ef270110 100644 --- a/pages/settings/profile.tsx +++ b/pages/settings/profile.tsx @@ -1,5 +1,5 @@ -import { GetServerSideProps } from "next"; -import { useEffect, useRef, useState } from "react"; +import { GetServerSidePropsContext } from "next"; +import { RefObject, useEffect, useRef, useState } from "react"; import prisma from "@lib/prisma"; import Modal from "@components/Modal"; import Shell from "@components/Shell"; @@ -12,19 +12,80 @@ import { UsernameInput } from "@components/ui/UsernameInput"; import ErrorAlert from "@components/ui/alerts/Error"; import ImageUploader from "@components/ImageUploader"; import crypto from "crypto"; +import { inferSSRProps } from "@lib/types/inferSSRProps"; +import Badge from "@components/ui/Badge"; +import Button from "@components/ui/Button"; +import { isBrandingHidden } from "@lib/isBrandingHidden"; const themeOptions = [ { value: "light", label: "Light" }, { value: "dark", label: "Dark" }, ]; -export default function Settings(props) { +type Props = inferSSRProps; +function HideBrandingInput(props: { + // + hideBrandingRef: RefObject; + user: Props["user"]; +}) { + const [modelOpen, setModalOpen] = useState(false); + return ( + <> + { + if (!e.currentTarget.checked || props.user.plan !== "FREE") { + return; + } + + // prevent checking the input + e.preventDefault(); + + setModalOpen(true); + }} + /> + + +

+ In order to remove the Calendso branding from your booking pages, you need to upgrade to a paid + account. +

+ +

+ {" "} + To upgrade go to{" "} + + calendso.com/upgrade + + . +

+
+ } + open={modelOpen} + handleClose={() => setModalOpen(false)} + /> + + ); +} + +export default function Settings(props: Props) { const [successModalOpen, setSuccessModalOpen] = useState(false); - const usernameRef = useRef(); - const nameRef = useRef(); + const usernameRef = useRef(null); + const nameRef = useRef(null); const descriptionRef = useRef(); - const avatarRef = useRef(); - const hideBrandingRef = useRef(); + const avatarRef = useRef(null); + const hideBrandingRef = useRef(null); const [selectedTheme, setSelectedTheme] = useState({ value: props.user.theme }); const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone }); const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({ value: props.user.weekStart }); @@ -244,18 +305,12 @@ export default function Settings(props) {
- +

Hide all Calendso branding from your public pages.

@@ -302,11 +357,7 @@ export default function Settings(props) {

- +
@@ -321,9 +372,9 @@ export default function Settings(props) { ); } -export const getServerSideProps: GetServerSideProps = async (context) => { +export const getServerSideProps = async (context: GetServerSidePropsContext) => { const session = await getSession(context); - if (!session) { + if (!session?.user?.id) { return { redirect: { permanent: false, destination: "/auth/login" } }; } @@ -342,9 +393,13 @@ export const getServerSideProps: GetServerSideProps = async (context) => { weekStart: true, hideBranding: true, theme: true, + plan: true, }, }); + if (!user) { + throw new Error("User seems logged in but cannot be found in the db"); + } return { props: { user: { diff --git a/pages/success.tsx b/pages/success.tsx index 0d2ddc1090..a986f10c0e 100644 --- a/pages/success.tsx +++ b/pages/success.tsx @@ -1,6 +1,6 @@ import { HeadSeo } from "@components/seo/head-seo"; import Link from "next/link"; -import prisma, { whereAndSelect } from "@lib/prisma"; +import prisma from "@lib/prisma"; import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { CheckIcon } from "@heroicons/react/outline"; @@ -12,12 +12,16 @@ import timezone from "dayjs/plugin/timezone"; import { createEvent } from "ics"; import { getEventName } from "@lib/event"; import Theme from "@components/Theme"; +import { GetServerSidePropsContext } from "next"; +import { asStringOrNull } from "../lib/asStringOrNull"; +import { inferSSRProps } from "@lib/types/inferSSRProps"; +import { isBrandingHidden } from "@lib/isBrandingHidden"; dayjs.extend(utc); dayjs.extend(toArray); dayjs.extend(timezone); -export default function Success(props) { +export default function Success(props: inferSSRProps) { const router = useRouter(); const { location, name } = router.query; @@ -220,7 +224,7 @@ export default function Success(props) { )} - {!props.user.hideBranding && ( + {!isBrandingHidden(props.user) && ( @@ -235,30 +239,52 @@ export default function Success(props) { ); } -export async function getServerSideProps(context) { - const user = context.query.user - ? await whereAndSelect( - prisma.user.findFirst, - { - username: context.query.user, - }, - ["username", "name", "bio", "avatar", "hideBranding", "theme"] - ) - : null; - - if (!user) { +export async function getServerSideProps(context: GetServerSidePropsContext) { + const username = asStringOrNull(context.query.user); + const typeId = parseInt(asStringOrNull(context.query.type) ?? ""); + if (!username || isNaN(typeId)) { return { notFound: true, }; } - const eventType = await whereAndSelect( - prisma.eventType.findUnique, - { - id: parseInt(context.query.type), + const user = await prisma.user.findUnique({ + where: { + username, }, - ["id", "title", "description", "length", "eventName", "requiresConfirmation"] - ); + select: { + username: true, + name: true, + bio: true, + avatar: true, + hideBranding: true, + theme: true, + plan: true, + }, + }); + if (!user) { + return { + notFound: true, + }; + } + const eventType = await prisma.eventType.findUnique({ + where: { + id: typeId, + }, + select: { + id: true, + title: true, + description: true, + length: true, + eventName: true, + requiresConfirmation: true, + }, + }); + if (!eventType) { + return { + notFound: true, + }; + } return { props: { diff --git a/yarn.lock b/yarn.lock index 3fc7b4f1fa..12e80f06a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7667,10 +7667,10 @@ typeorm@^0.2.30: yargs "^17.0.1" zen-observable-ts "^1.0.0" -typescript@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86" - integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ== +typescript@^4.4.3: + version "4.4.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" + integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA== uglify-js@^3.1.4: version "3.14.2"