From a9af2fb2554a6562aa55105ddcdecdffe9308a1f Mon Sep 17 00:00:00 2001 From: Leo Giovanetti Date: Tue, 7 Feb 2023 21:23:42 -0300 Subject: [PATCH] Admin Wizard Choose License (#6574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implementation * i18n * More i18n * extracted i18n, needs api to get most recent price, added hint: update later * Fixing i18n var * Fix booking filters not working for admin (#6576) * fix: react-select overflow issue in some modals. (#6587) * feat: add a disable overflow prop * feat: use the disable overflow prop * Tailwind Merge (#6596) * Tailwind Merge * Fix merge classNames * [CAL-808] /availability/single - UI issue on buttons beside time inputs (#6561) * [CAL-808] /availability/single - UI issue on buttons beside time inputs * Update apps/web/public/static/locales/en/common.json * Update packages/features/schedules/components/Schedule.tsx * create new translation for tooltip Co-authored-by: gitstart-calcom Co-authored-by: Peer Richelsen Co-authored-by: CarinaWolli * Bye bye submodules (#6585) * WIP * Uses ssh instead * Update .gitignore * Update .gitignore * Update Makefile * Update git-setup.sh * Update git-setup.sh * Replaced Makefile with bash script * Update package.json * fix: show button on empty string (#6601) Signed-off-by: Udit Takkar Signed-off-by: Udit Takkar * fix: add delete in dropdown (#6599) Signed-off-by: Udit Takkar Signed-off-by: Udit Takkar * Update README.md * Update README.md * Changed a neutral- classes to gray (#6603) * Changed a neutral- classes to gray * Changed all border-1 to border * Update package.json * Test fixes * Yarn lock fixes * Fix string equality check in git-setup.sh * [CAL-811] Avatar icon not redirecting user back to the main page (#6586) * Remove cursor-pointer, remove old Avatar* files * Fixed styling for checkedSelect + some cleanup Co-authored-by: gitstart-calcom Co-authored-by: Alex van Andel * Harsh/add member invite (#6598) Co-authored-by: Guest Co-authored-by: root * Regenerated lockfile without upgrade (#6610) * fix: remove annoying outline when +
+
+ OR +
+ {t("already_have_key")} + ( + + ) : errors.licenseKey === undefined && isDirty ? ( + + ) : undefined + } + color={errors.licenseKey ? "warn" : ""} + onBlur={onBlur} + onChange={async (e: React.ChangeEvent) => { + onChange(e.target.value); + formMethods.setValue("licenseKey", e.target.value); + await formMethods.trigger("licenseKey"); + }} + /> + )} + /> + + + + ); +}; + +export default EnterpriseLicense; diff --git a/apps/web/pages/auth/setup/steps/StepDone.tsx b/apps/web/components/setup/StepDone.tsx similarity index 67% rename from apps/web/pages/auth/setup/steps/StepDone.tsx rename to apps/web/components/setup/StepDone.tsx index 3019ffeed0..88c8b004c7 100644 --- a/apps/web/pages/auth/setup/steps/StepDone.tsx +++ b/apps/web/components/setup/StepDone.tsx @@ -1,20 +1,26 @@ import { useRouter } from "next/router"; +import { Dispatch, SetStateAction } from "react"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { FiCheck } from "@calcom/ui/components/icon"; -const StepDone = () => { +const StepDone = (props: { + currentStep: number; + nextStepPath: string; + setIsLoading: Dispatch>; +}) => { const router = useRouter(); const { t } = useLocale(); return (
{ + props.setIsLoading(true); e.preventDefault(); - router.replace(`/auth/setup?step=2&category=calendar`); + router.replace(props.nextStepPath); }}>
diff --git a/apps/web/pages/api/auth/[...nextauth].tsx b/apps/web/pages/api/auth/[...nextauth].tsx index 20c5147391..ce59414763 100644 --- a/apps/web/pages/api/auth/[...nextauth].tsx +++ b/apps/web/pages/api/auth/[...nextauth].tsx @@ -360,7 +360,7 @@ export default NextAuth({ return token; }, async session({ session, token }) { - const hasValidLicense = await checkLicense(process.env.CALCOM_LICENSE_KEY || ""); + const hasValidLicense = await checkLicense(prisma); const calendsoSession: Session = { ...session, hasValidLicense, diff --git a/apps/web/pages/auth/setup/index.tsx b/apps/web/pages/auth/setup/index.tsx index 32f39baa76..7488bd21ec 100644 --- a/apps/web/pages/auth/setup/index.tsx +++ b/apps/web/pages/auth/setup/index.tsx @@ -1,40 +1,125 @@ import { UserPermissionRole } from "@prisma/client"; import { GetServerSidePropsContext } from "next"; +import { useRouter } from "next/router"; import { useState } from "react"; import AdminAppsList from "@calcom/features/apps/AdminAppsList"; +import { getDeploymentKey } from "@calcom/features/ee/deployment/lib/getDeploymentKey"; import { getSession } from "@calcom/lib/auth"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import prisma from "@calcom/prisma"; import { inferSSRProps } from "@calcom/types/inferSSRProps"; -import { WizardForm } from "@calcom/ui"; +import { Meta, WizardForm } from "@calcom/ui"; -import SetupFormStep1 from "./steps/SetupFormStep1"; -import StepDone from "./steps/StepDone"; +import { AdminUserContainer as AdminUser } from "@components/setup/AdminUser"; +import ChooseLicense from "@components/setup/ChooseLicense"; +import EnterpriseLicense from "@components/setup/EnterpriseLicense"; + +import { ssrInit } from "@server/lib/ssr"; export default function Setup(props: inferSSRProps) { const { t } = useLocale(); - const [isLoadingStep1, setIsLoadingStep1] = useState(false); - const shouldDisable = props.userCount !== 0; + const router = useRouter(); + const [value, setValue] = useState(props.isFreeLicense ? "FREE" : "EE"); + const isFreeLicense = value === "FREE"; + const [isEnabledEE, setIsEnabledEE] = useState(!props.isFreeLicense); + const setStep = (newStep: number) => { + router.replace(`/auth/setup?step=${newStep || 1}`, undefined, { shallow: true }); + }; - const steps = [ + const steps: React.ComponentProps["steps"] = [ { title: t("administrator_user"), description: t("lets_create_first_administrator_user"), - content: shouldDisable ? : , - isLoading: isLoadingStep1, + content: (setIsLoading) => ( + { + setIsLoading(true); + }} + onSuccess={() => { + setStep(2); + }} + onError={() => { + setIsLoading(false); + }} + userCount={props.userCount} + /> + ), }, { - title: t("enable_apps"), - description: t("enable_apps_description"), - content: , - isLoading: false, + title: t("choose_a_license"), + description: t("choose_license_description"), + content: (setIsLoading) => { + return ( + { + setIsLoading(true); + setStep(3); + }} + /> + ); + }, }, ]; + if (!isFreeLicense) { + steps.push({ + title: t("step_enterprise_license"), + description: t("step_enterprise_license_description"), + content: (setIsLoading) => { + const currentStep = 3; + return ( + { + setIsLoading(true); + }} + onSuccess={() => { + setStep(currentStep + 1); + }} + onSuccessValidate={() => { + setIsEnabledEE(true); + }} + /> + ); + }, + isEnabled: isEnabledEE, + }); + } + + steps.push({ + title: t("enable_apps"), + description: t("enable_apps_description"), + contentClassname: "!pb-0 mb-[-1px]", + content: (setIsLoading) => { + const currentStep = isFreeLicense ? 3 : 4; + return ( + { + setIsLoading(true); + router.replace("/"); + }} + /> + ); + }, + }); + return ( <> -
+ +
) { finishLabel={t("finish")} prevLabel={t("prev_step")} stepLabel={(currentStep, maxSteps) => t("current_step_of_total", { currentStep, maxSteps })} + containerClassname="md:w-[800px]" />
@@ -49,6 +135,7 @@ export default function Setup(props: inferSSRProps) { } export const getServerSideProps = async (context: GetServerSidePropsContext) => { + const ssr = await ssrInit(context); const userCount = await prisma.user.count(); const { req } = context; const session = await getSession({ req }); @@ -62,8 +149,30 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => }; } + let deploymentKey = await getDeploymentKey(prisma); + + // Check existant CALCOM_LICENSE_KEY env var and acccount for it + if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey) { + await prisma.deployment.upsert({ + where: { id: 1 }, + update: { + licenseKey: process.env.CALCOM_LICENSE_KEY, + agreedLicenseAt: new Date(), + }, + create: { + licenseKey: process.env.CALCOM_LICENSE_KEY, + agreedLicenseAt: new Date(), + }, + }); + deploymentKey = await getDeploymentKey(prisma); + } + + const isFreeLicense = deploymentKey === ""; + return { props: { + trpcState: ssr.dehydrate(), + isFreeLicense, userCount, }, }; diff --git a/apps/web/pages/getting-started/[[...step]].tsx b/apps/web/pages/getting-started/[[...step]].tsx index b8703e0341..88f457b456 100644 --- a/apps/web/pages/getting-started/[[...step]].tsx +++ b/apps/web/pages/getting-started/[[...step]].tsx @@ -105,7 +105,7 @@ const OnboardingPage = (props: IOnboardingPageProps) => {

))} - +
{currentStep === "user-settings" && goToIndex(1)} />} diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index e594ae171b..aa2134801e 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -515,6 +515,8 @@ "admin": "Admin", "administrator_user": "Administrator user", "lets_create_first_administrator_user": "Let's create the first administrator user.", + "admin_user_created": "Administrator user setup", + "admin_user_created_description": "You have already created an administrator user. You can now log in to your account.", "new_member": "New Member", "invite": "Invite", "add_team_members": "Add team members", @@ -796,7 +798,7 @@ "number_apps_one": "{{count}} App", "number_apps_other": "{{count}} Apps", "trending_apps": "Trending Apps", - "most_popular":"Most Popular", + "most_popular": "Most Popular", "explore_apps": "{{category}} apps", "installed_apps": "Installed Apps", "free_to_use_apps": "Free", @@ -841,7 +843,7 @@ "connect_metamask": "Connect Metamask", "create_events_on": "Create events on", "enterprise_license": "This is an enterprise feature", - "enterprise_license_description": "To enable this feature, get a deployment key at {{consoleUrl}} console and add it to your .env as CALCOM_LICENSE_KEY. If your team already has a license, please contact {{supportMail}} for help.", + "enterprise_license_description": "To enable this feature, have an administrator go to {{setupUrl}} to enter a license key. If a license key is already in place, please contact {{supportMail}} for help.", "missing_license": "Missing License", "signup_requires": "Commercial license required", "signup_requires_description": "{{companyName}} currently does not offer a free open source version of the sign up page. To receive full access to the signup components you need to acquire a commercial license. For personal use we recommend the Prisma Data Platform or any other Postgres interface to create accounts.", @@ -1114,8 +1116,8 @@ "close": "Close", "upgrade": "Upgrade", "upgrade_to_access_recordings_title": "Upgrade to access recordings", - "upgrade_to_access_recordings_description":"Recordings are only available as part of our teams plan. Upgrade to start recording your calls", - "recordings_are_part_of_the_teams_plan":"Recordings are part of the teams plan", + "upgrade_to_access_recordings_description": "Recordings are only available as part of our teams plan. Upgrade to start recording your calls", + "recordings_are_part_of_the_teams_plan": "Recordings are part of the teams plan", "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", @@ -1414,7 +1416,7 @@ "team_url_required": "Must enter a team URL", "team_url_taken": "This URL is already taken", "team_publish": "Publish team", - "number_sms_notifications": "Phone number (SMS\u00a0notifications)", + "number_sms_notifications": "Phone number (SMS notifications)", "attendee_email_variable": "Attendee email", "attendee_email_info": "The person booking's email", "kbar_search_placeholder": "Type a command or search...", @@ -1451,6 +1453,9 @@ "disabled_calendar": "If you have another calendar installed new bookings will be added to it. If not then connect a new calendar so you do not miss any new bookings.", "enable_apps": "Enable Apps", "enable_apps_description": "Enable apps that users can integrate with Cal.com", + "purchase_license": "Purchase a License", + "already_have_key": "I already have a key:", + "already_have_key_suggestion": "Please copy your existing CALCOM_LICENSE_KEY environment variable here.", "app_is_enabled": "{{appName}} is enabled", "app_is_disabled": "{{appName}} is disabled", "keys_have_been_saved": "Keys have been saved", @@ -1496,11 +1501,11 @@ "not_verified": "Not yet verified", "no_availability_in_month": "No availability in {{month}}", "view_next_month": "View next month", - "send_code" : "Send code", + "send_code": "Send code", "number_verified": "Number Verified", "create_your_first_team_webhook_description": "Create your first webhook for this team event type", "create_webhook_team_event_type": "Create a webhook for this team event type", - "disable_success_page":"Disable Success Page (only works if you have a redirect URL)", + "disable_success_page": "Disable Success Page (only works if you have a redirect URL)", "invalid_admin_password": "You are set as an admin but you do not have a password length of at least 15 characters", "change_password_admin": "Change Password to gain admin access", "username_already_taken": "Username is already taken", @@ -1526,6 +1531,25 @@ "reporting_feature": "See all incoming from data and download it as a CSV", "teams_plan_required": "Teams plan required", "routing_forms_are_a_great_way": "Routing forms are a great way to route your incoming leads to the right person. Upgrade to a Teams plan to access this feature.", + "choose_a_license": "Choose a license", + "choose_license_description": "Cal.com comes with an accessible and free AGPLv3 license which some limitations which can be upgraded to an Enterprise license at any time. You can upgrade at anytime later.", + "license": "License", + "agplv3_license": "AGPLv3 License", + "ee_enterprise_license": "“/ee” Enterprise License", + "enterprise_booking_fee": "Starting at {{enterprise_booking_fee}}/month", + "enterprise_license_includes": "Everything for a commercial use case", + "no_need_to_keep_your_code_open_source": "No need to keep your code open-source", + "repackage_rebrand_resell": "Repackage, rebrand and resell easily", + "a_vast_suite_of_enterprise_features": "A vast suite of enterprise features", + "free_license_fee": "$0.00/month", + "forever_open_and_free": "Forever Open & Free", + "required_to_keep_your_code_open_source": "Required to keep your code open source", + "cannot_repackage_and_resell": "Cannot repackage, rebrand and resell easily", + "no_enterprise_features": "No enterprise features", + "step_enterprise_license": "Enterprise License", + "step_enterprise_license_description": "Everything for a commercial use case with private hosting, repackaging, rebranding and reselling and access exclusive enterprise components.", + "setup": "Setup", + "setup_description": "Setup Cal.com instance", "configure": "Configure", "sso_configuration": "Single Sign-On", "sso_configuration_description": "Configure SAML/OIDC SSO and allow team members to login using an Identity Provider", diff --git a/packages/app-store/_components/AppCategoryNavigation.tsx b/packages/app-store/_components/AppCategoryNavigation.tsx index 1b8b3202bc..dbf6a141f9 100644 --- a/packages/app-store/_components/AppCategoryNavigation.tsx +++ b/packages/app-store/_components/AppCategoryNavigation.tsx @@ -1,7 +1,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useMemo } from "react"; -import { classNames } from "@calcom/lib"; +import { classNames as cs } from "@calcom/lib"; import { HorizontalTabs, VerticalTabs } from "@calcom/ui"; import getAppCategories from "../_utils/getAppCategories"; @@ -11,33 +11,39 @@ const AppCategoryNavigation = ({ children, containerClassname, className, - fromAdmin, + classNames, useQueryParam = false, }: { baseURL: string; children: React.ReactNode; - containerClassname: string; + /** @deprecated use classNames instead */ + containerClassname?: string; + /** @deprecated use classNames instead */ className?: string; - fromAdmin?: boolean; + classNames?: { + root?: string; + container?: string; + verticalTabsItem?: string; + }; useQueryParam?: boolean; }) => { const [animationRef] = useAutoAnimate(); const appCategories = useMemo(() => getAppCategories(baseURL, useQueryParam), [baseURL, useQueryParam]); return ( -
+
-
+
{children}
diff --git a/packages/config/tailwind-preset.js b/packages/config/tailwind-preset.js index 851aa8bab9..780e9a5796 100644 --- a/packages/config/tailwind-preset.js +++ b/packages/config/tailwind-preset.js @@ -6,7 +6,7 @@ module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", - "../../packages/app-store/**/{components,pages}/**/*.{js,ts,jsx,tsx}", + "../../packages/app-store/**/*{components,pages}/**/*.{js,ts,jsx,tsx}", "../../packages/features/**/*.{js,ts,jsx,tsx}", "../../packages/ui/**/*.{js,ts,jsx,tsx}", ], diff --git a/packages/features/apps/AdminAppsList.tsx b/packages/features/apps/AdminAppsList.tsx index 366ecccbec..6d421297bf 100644 --- a/packages/features/apps/AdminAppsList.tsx +++ b/packages/features/apps/AdminAppsList.tsx @@ -1,12 +1,14 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { AppCategories } from "@prisma/client"; +import { noop } from "lodash"; import { useRouter } from "next/router"; -import { useState, useReducer, FC } from "react"; +import { FC, useReducer, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation"; import { appKeysSchemas } from "@calcom/app-store/apps.keys-schemas.generated"; +import { classNames as cs } from "@calcom/lib"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { @@ -32,9 +34,9 @@ import { } from "@calcom/ui"; import { FiAlertCircle, + FiCheckCircle, FiEdit, FiMoreHorizontal, - FiCheckCircle, FiXCircle, } from "@calcom/ui/components/icon"; @@ -148,26 +150,39 @@ const AdminAppsList = ({ baseURL, className, useQueryParam = false, + classNames, + onSubmit = noop, + ...rest }: { baseURL: string; + classNames?: { + form?: string; + appCategoryNavigationRoot?: string; + appCategoryNavigationContainer?: string; + verticalTabsItem?: string; + }; className?: string; useQueryParam?: boolean; -}) => { - const router = useRouter(); + onSubmit?: () => void; +} & Omit) => { return ( { e.preventDefault(); - router.replace("/"); + onSubmit(); }}> + classNames={{ + root: className, + verticalTabsItem: classNames?.verticalTabsItem, + container: cs("min-w-0 w-full", classNames?.appCategoryNavigationContainer ?? "max-w-[500px]"), + }}> diff --git a/packages/features/ee/api-keys/components/ApiKeyListItem.tsx b/packages/features/ee/api-keys/components/ApiKeyListItem.tsx index 5670ade914..080e6d1bed 100644 --- a/packages/features/ee/api-keys/components/ApiKeyListItem.tsx +++ b/packages/features/ee/api-keys/components/ApiKeyListItem.tsx @@ -43,16 +43,8 @@ const ApiKeyListItem = ({

{apiKey?.note ? apiKey.note : t("api_key_no_note")}

- {!neverExpires && isExpired && ( - - {t("expired")} - - )} - {!isExpired && ( - - {t("active")} - - )} + {!neverExpires && isExpired && {t("expired")}} + {!isExpired && {t("active")}}

{" "} {neverExpires ? ( diff --git a/packages/features/ee/common/components/LicenseRequired.tsx b/packages/features/ee/common/components/LicenseRequired.tsx index ab44ea987c..bc3664a9d9 100644 --- a/packages/features/ee/common/components/LicenseRequired.tsx +++ b/packages/features/ee/common/components/LicenseRequired.tsx @@ -7,7 +7,7 @@ import DOMPurify from "dompurify"; import { useSession } from "next-auth/react"; import React, { AriaRole, ComponentType, Fragment } from "react"; -import { APP_NAME } from "@calcom/lib/constants"; +import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { EmptyScreen } from "@calcom/ui"; import { FiAlertTriangle } from "@calcom/ui/components/icon"; @@ -46,6 +46,7 @@ const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) = consoleUrl: ` ${APP_NAME} `, + setupUrl: `/auth/setup`, supportMail: ` sales@cal.com `, diff --git a/packages/features/ee/common/components/v2/LicenseRequired.tsx b/packages/features/ee/common/components/v2/LicenseRequired.tsx index 56d426e440..4b366df547 100644 --- a/packages/features/ee/common/components/v2/LicenseRequired.tsx +++ b/packages/features/ee/common/components/v2/LicenseRequired.tsx @@ -2,7 +2,7 @@ import DOMPurify from "dompurify"; import { useSession } from "next-auth/react"; import React, { AriaRole, ComponentType, Fragment } from "react"; -import { APP_NAME, CONSOLE_URL, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants"; +import { APP_NAME, CONSOLE_URL, SUPPORT_MAIL_ADDRESS, WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { EmptyScreen } from "@calcom/ui"; import { FiAlertTriangle } from "@calcom/ui/components/icon"; @@ -36,9 +36,9 @@ const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) = consoleUrl: ` ${APP_NAME} `, + setupUrl: `/auth/setup`, supportMail: ` - ${SUPPORT_MAIL_ADDRESS} - `, + ${SUPPORT_MAIL_ADDRESS}`, }) ), }} diff --git a/packages/features/ee/common/server/checkLicense.ts b/packages/features/ee/common/server/checkLicense.ts index 961c66e2ce..82501b43a9 100644 --- a/packages/features/ee/common/server/checkLicense.ts +++ b/packages/features/ee/common/server/checkLicense.ts @@ -1,3 +1,4 @@ +import type { PrismaClient } from "@prisma/client"; import cache from "memory-cache"; import { z } from "zod"; @@ -18,10 +19,21 @@ const schemaLicenseKey = z : v; }); -async function checkLicense(license: string): Promise { +async function checkLicense( + /** The prisma client to use (necessary for public API to handle custom prisma instances) */ + prisma: PrismaClient +): Promise { + /** We skip for E2E testing */ if (!!process.env.NEXT_PUBLIC_IS_E2E) return true; - if (!license) return false; - const url = `${CONSOLE_URL}/api/license?key=${schemaLicenseKey.parse(license)}`; + /** We check first on env */ + let licenseKey = process.env.CALCOM_LICENSE_KEY; + if (!licenseKey) { + /** We try to check on DB only if env is undefined */ + const deployment = await prisma.deployment.findFirst({ where: { id: 1 } }); + licenseKey = deployment?.licenseKey ?? undefined; + } + if (!licenseKey) return false; + const url = `${CONSOLE_URL}/api/license?key=${schemaLicenseKey.parse(licenseKey)}`; const cachedResponse = cache.get(url); if (cachedResponse) { return cachedResponse; diff --git a/packages/features/ee/deployment/lib/getDeploymentKey.ts b/packages/features/ee/deployment/lib/getDeploymentKey.ts new file mode 100644 index 0000000000..3c25255a52 --- /dev/null +++ b/packages/features/ee/deployment/lib/getDeploymentKey.ts @@ -0,0 +1,9 @@ +import type { PrismaClient } from "@prisma/client"; + +export async function getDeploymentKey(prisma: PrismaClient) { + const deployment = await prisma.deployment.findUnique({ + where: { id: 1 }, + select: { licenseKey: true }, + }); + return deployment?.licenseKey || process.env.CALCOM_LICENSE_KEY || ""; +} diff --git a/packages/features/kbar/Kbar.tsx b/packages/features/kbar/Kbar.tsx index f08a7eeb5e..4b491d6a05 100644 --- a/packages/features/kbar/Kbar.tsx +++ b/packages/features/kbar/Kbar.tsx @@ -209,6 +209,14 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => { keywords: "user impersonation", perform: () => router.push("/settings/security/impersonation"), }, + { + id: "license", + name: "choose_a_license", + section: "admin", + shortcut: ["u", "l"], + keywords: "license", + perform: () => router.push("/auth/setup?step=1"), + }, { id: "webhooks", name: "Webhooks", diff --git a/packages/features/settings/layouts/SettingsLayout.tsx b/packages/features/settings/layouts/SettingsLayout.tsx index c4b3baa311..7655b66d90 100644 --- a/packages/features/settings/layouts/SettingsLayout.tsx +++ b/packages/features/settings/layouts/SettingsLayout.tsx @@ -32,6 +32,7 @@ import { FiChevronRight, FiPlus, FiMenu, + FiExternalLink, } from "@calcom/ui/components/icon"; const tabs: VerticalTabItemProps[] = [ @@ -89,6 +90,7 @@ const tabs: VerticalTabItemProps[] = [ icon: FiLock, children: [ // + { name: "license", href: "/auth/setup?step=1" }, { name: "impersonation", href: "/settings/admin/impersonation" }, { name: "apps", href: "/settings/admin/apps/calendar" }, { name: "users", href: "https://console.cal.com" }, diff --git a/packages/trpc/server/routers/viewer.tsx b/packages/trpc/server/routers/viewer.tsx index 1847c11812..23fac562dd 100644 --- a/packages/trpc/server/routers/viewer.tsx +++ b/packages/trpc/server/routers/viewer.tsx @@ -41,6 +41,7 @@ import { appsRouter } from "./viewer/apps"; import { authRouter } from "./viewer/auth"; import { availabilityRouter } from "./viewer/availability"; import { bookingsRouter } from "./viewer/bookings"; +import { deploymentSetupRouter } from "./viewer/deploymentSetup"; import { eventTypesRouter } from "./viewer/eventTypes"; import { slotsRouter } from "./viewer/slots"; import { ssoRouter } from "./viewer/sso"; @@ -1150,6 +1151,7 @@ export const viewerRouter = mergeRouters( loggedInViewerRouter, public: publicViewerRouter, auth: authRouter, + deploymentSetup: deploymentSetupRouter, bookings: bookingsRouter, eventTypes: eventTypesRouter, availability: availabilityRouter, diff --git a/packages/trpc/server/routers/viewer/deploymentSetup.tsx b/packages/trpc/server/routers/viewer/deploymentSetup.tsx new file mode 100644 index 0000000000..8b1898cfb6 --- /dev/null +++ b/packages/trpc/server/routers/viewer/deploymentSetup.tsx @@ -0,0 +1,24 @@ +import { z } from "zod"; + +import prisma from "@calcom/prisma"; + +import { router, authedAdminProcedure } from "../../trpc"; + +export const deploymentSetupRouter = router({ + update: authedAdminProcedure + .input( + z.object({ + licenseKey: z.string().optional(), + }) + ) + .mutation(async ({ input }) => { + const data = { + agreedLicenseAt: new Date(), + licenseKey: input.licenseKey, + }; + + await prisma.deployment.upsert({ where: { id: 1 }, create: data, update: data }); + + return; + }), +}); diff --git a/packages/types/environment.d.ts b/packages/types/environment.d.ts index 3f14b8fcb7..a186d1d9be 100644 --- a/packages/types/environment.d.ts +++ b/packages/types/environment.d.ts @@ -1,7 +1,5 @@ declare namespace NodeJS { interface ProcessEnv { - /** Needed to enable enterprise-only features */ - readonly CALCOM_LICENSE_KEY: string | undefined; readonly CALCOM_TELEMETRY_DISABLED: string | undefined; readonly CALENDSO_ENCRYPTION_KEY: string | undefined; readonly DATABASE_URL: string | undefined; diff --git a/packages/ui/components/button/Button.tsx b/packages/ui/components/button/Button.tsx index 91c17f2511..ca1c93697e 100644 --- a/packages/ui/components/button/Button.tsx +++ b/packages/ui/components/button/Button.tsx @@ -211,7 +211,7 @@ export const Button = forwardRef )} diff --git a/packages/ui/components/form/inputs/Input.tsx b/packages/ui/components/form/inputs/Input.tsx index a1176460c0..e5a36b9701 100644 --- a/packages/ui/components/form/inputs/Input.tsx +++ b/packages/ui/components/form/inputs/Input.tsx @@ -117,7 +117,7 @@ export const InputField = forwardRef(function )} {addOnLeading || addOnSuffix ? ( -

+
{addOnLeading && ( (props: { steps, stepLabel = (currentStep, totalSteps) => `Step ${currentStep} of ${totalSteps}`, } = props; + const [stepperRef] = useAutoAnimate(); return ( <> {steps.length > 1 && (