diff --git a/src/packages/database/settings/customize.ts b/src/packages/database/settings/customize.ts index 549431f49b..a78cf06dc3 100644 --- a/src/packages/database/settings/customize.ts +++ b/src/packages/database/settings/customize.ts @@ -77,6 +77,7 @@ export default async function getCustomize( imprint: settings.imprint, policies: settings.policies, support: settings.support, + supportVideoCall: settings.support_video_call, // Is important for invite emails, password reset, etc. (e.g., so we can construct a url to our site). // This *can* start with http:// to explicitly use http instead of https, and can end diff --git a/src/packages/next/components/auth/sign-up.tsx b/src/packages/next/components/auth/sign-up.tsx index 4e7a2f55b7..ce1987b6b3 100644 --- a/src/packages/next/components/auth/sign-up.tsx +++ b/src/packages/next/components/auth/sign-up.tsx @@ -3,12 +3,13 @@ * License: MS-RSL – see LICENSE.md for details */ -import { Alert, Button, Checkbox, Input } from "antd"; +import { Alert, Button, Checkbox, Divider, Input } from "antd"; import { CSSProperties, useEffect, useRef, useState } from "react"; import { GoogleReCaptchaProvider, useGoogleReCaptcha, } from "react-google-recaptcha-v3"; + import Markdown from "@cocalc/frontend/editors/slate/static-markdown"; import { CONTACT_TAG, @@ -22,6 +23,7 @@ import { } from "@cocalc/util/misc"; import { COLORS } from "@cocalc/util/theme"; import { Strategy } from "@cocalc/util/types/sso"; +import { Paragraph } from "components/misc"; import A from "components/misc/A"; import Loading from "components/share/loading"; import apiPost from "lib/api/post"; @@ -99,7 +101,7 @@ function SignUp0({ const submittable = useRef(false); const { executeRecaptcha } = useGoogleReCaptcha(); - const { strategies } = useCustomize(); + const { strategies, supportVideoCall } = useCustomize(); // Sometimes the user if this component knows requiresToken and sometimes they don't. // If they don't, we have to make an API call to figure it out. @@ -263,13 +265,21 @@ function SignUp0({ minimal={minimal} title={`Create a free account with ${siteName}`} > -
+ By creating an account, you agree to the{" "} Terms of Service . -
+ + {onCoCalcCom && supportVideoCall ? ( + + Do you need more information how {siteName} can be useful for you?{" "} + Book a video call and we'll help you + decide. + + ) : undefined} + {!minimal && onCoCalcCom ? ( - + diff --git a/src/packages/next/components/landing/footer.tsx b/src/packages/next/components/landing/footer.tsx index 5aed4020b3..39990fd8b7 100644 --- a/src/packages/next/components/landing/footer.tsx +++ b/src/packages/next/components/landing/footer.tsx @@ -62,7 +62,7 @@ export default function Footer() { organizationURL, enabledPages, termsOfServiceURL, - account, + supportVideoCall, } = useCustomize(); const footerColumns: Array = [ @@ -140,8 +140,13 @@ export default function Footer() { }, { text: "Get a Live Demo", + url: supportVideoCall ?? "", + hide: !enabledPages?.liveDemo || !supportVideoCall, + }, + { + text: "Contact Us", url: liveDemoUrl("footer"), - hide: !account || !enabledPages?.liveDemo, + hide: !enabledPages?.support, }, ], }, diff --git a/src/packages/next/components/landing/header.tsx b/src/packages/next/components/landing/header.tsx index f881474942..d9ec8ff645 100644 --- a/src/packages/next/components/landing/header.tsx +++ b/src/packages/next/components/landing/header.tsx @@ -72,7 +72,7 @@ export default function Header(props: Props) { }} > {true || account ? ( - + ) : ( ); } diff --git a/src/packages/next/components/landing/sign-in.tsx b/src/packages/next/components/landing/sign-in.tsx index 262e0afe44..9bb4e2e3bc 100644 --- a/src/packages/next/components/landing/sign-in.tsx +++ b/src/packages/next/components/landing/sign-in.tsx @@ -8,6 +8,7 @@ import { useRouter } from "next/router"; import { join } from "path"; import { CSSProperties, ReactNode } from "react"; +import { Icon } from "@cocalc/frontend/components/icon"; import SSO from "components/auth/sso"; import { Paragraph } from "components/misc"; import basePath from "lib/base-path"; @@ -17,6 +18,7 @@ interface Props { startup?: ReactNode; // customize the button, e.g. "Start Jupyter Now". hideFree?: boolean; style?: React.CSSProperties; + emphasize?: boolean; } const STYLE: CSSProperties = { @@ -25,7 +27,7 @@ const STYLE: CSSProperties = { marginBottom: "0", } as const; -export default function SignIn({ startup, hideFree, style }: Props) { +export default function SignIn({ startup, hideFree, style, emphasize }: Props) { const { anonymousSignup, siteName, account, emailSignup } = useCustomize(); style = { ...STYLE, ...style }; const router = useRouter(); @@ -38,6 +40,7 @@ export default function SignIn({ startup, hideFree, style }: Props) { onClick={() => (window.location.href = join(basePath, "projects"))} title={`Open the ${siteName} app and view your projects.`} type="primary" + icon={} > Your {siteName} Projects @@ -55,6 +58,7 @@ export default function SignIn({ startup, hideFree, style }: Props) { style={{ margin: "10px" }} title={"Create a new account."} onClick={() => router.push("/auth/sign-up")} + type={emphasize ? "primary" : undefined} > Sign Up @@ -65,6 +69,7 @@ export default function SignIn({ startup, hideFree, style }: Props) { "Either create a new account or sign into an existing account." } onClick={() => router.push("/auth/sign-in")} + type={emphasize ? "primary" : undefined} > Sign In diff --git a/src/packages/next/components/store/overview.tsx b/src/packages/next/components/store/overview.tsx index cfc378065c..f3cf3c8bb6 100644 --- a/src/packages/next/components/store/overview.tsx +++ b/src/packages/next/components/store/overview.tsx @@ -6,10 +6,12 @@ import { Divider } from "antd"; import { useRouter } from "next/router"; import { useEffect } from "react"; + import { Icon, PAYASYOUGO_ICON } from "@cocalc/frontend/components/icon"; import { Paragraph } from "components/misc"; import A from "components/misc/A"; import SiteName from "components/share/site-name"; +import { useCustomize } from "lib/customize"; import { OVERVIEW_LARGE_ICON, OVERVIEW_STYLE, @@ -19,6 +21,7 @@ import { export default function Overview() { const router = useRouter(); + const { supportVideoCall } = useCustomize(); // most likely, user will go to the cart next useEffect(() => { @@ -31,11 +34,18 @@ export default function Overview() {

Welcome to the Store!

-
+ Shop below for licenses and{" "} vouchers or explore{" "} all available products and pricing. -
+ + {supportVideoCall ? ( + + Not sure what you need?{" "} + Book a video call and we'll help you + decide. + + ) : undefined} Buy a license to upgrade projects, get internet access, more CPU, disk diff --git a/src/packages/next/components/support/create.tsx b/src/packages/next/components/support/create.tsx index 9308096035..c721507e52 100644 --- a/src/packages/next/components/support/create.tsx +++ b/src/packages/next/components/support/create.tsx @@ -10,6 +10,7 @@ import { } from "antd"; import { useRouter } from "next/router"; import { ReactNode, useRef, useState } from "react"; + import { Icon } from "@cocalc/frontend/components/icon"; import { is_valid_email_address as isValidEmailAddress } from "@cocalc/util/misc"; import { COLORS } from "@cocalc/util/theme"; @@ -19,6 +20,7 @@ import ChatGPTHelp from "components/openai/chatgpt-help"; import CodeMirror from "components/share/codemirror"; import Loading from "components/share/loading"; import SiteName from "components/share/site-name"; +import { VideoItem } from "components/videos"; import apiPost from "lib/api/post"; import { MAX_WIDTH } from "lib/config"; import { useCustomize } from "lib/customize"; @@ -26,7 +28,6 @@ import getBrowserInfo from "./browser-info"; import RecentFiles from "./recent-files"; import { Type } from "./tickets"; import { NoZendesk } from "./util"; -import { VideoItem } from "components/videos"; const CHATGPT_DISABLED = true; const MIN_BODY_LENGTH = 16; @@ -43,19 +44,26 @@ export type Type = "problem" | "question" | "task" | "purchase" | "chat"; function stringToType(s?: any): Type { if ( - s == "problem" || - s == "question" || - s == "task" || - s == "purchase" || - s == "chat" + s === "problem" || + s === "question" || + s === "task" || + s === "purchase" || + s === "chat" ) return s; return "problem"; // default; } export default function Create() { - const { account, onCoCalcCom, helpEmail, openaiEnabled, siteName, zendesk } = - useCustomize(); + const { + account, + onCoCalcCom, + helpEmail, + openaiEnabled, + siteName, + zendesk, + supportVideoCall, + } = useCustomize(); const router = useRouter(); // The URL the user was viewing when they requested support. // This could easily be blank, but if it is set it can be useful. @@ -131,6 +139,61 @@ export default function Create() { ); } + function renderChat() { + if (type === "chat" && supportVideoCall) { + return ( + + Please describe what you want to discuss in the{" "} + video chat. We will then contact + you to confirm the time. + + } + message={ + + <Icon name="video-camera" /> You can{" "} + <A href={supportVideoCall}>book a video chat</A> with us. + + } + /> + ); + } else { + return ( + <> + + = MIN_BODY_LENGTH && hasRequired} + />{" "} + Description + +
+ {type === "problem" && } + {type === "question" && ( + + )} + {type === "purchase" && ( + + )} + {type === "task" && } +
+ + ); + } + } + return (
-

- Create a new support ticket below or{" "} - - check the status of your support tickets - - .{" "} + + + Create a new support ticket below or{" "} + + check the status of your support tickets + + . + {helpEmail ? ( - <> + You can also email us directly at{" "} - {helpEmail} or{" "} - - book a demo or discovery call - - . - + {helpEmail}. + + ) : undefined} + {supportVideoCall ? ( + + Alternatively, feel free to{" "} + book a video call with us. + ) : undefined} -

+
)} - {type == "chat" && ( -

- - Book a Video Chat... - -

- )} - {type != "chat" && ( - <> - - = MIN_BODY_LENGTH && hasRequired} - />{" "} - Description - -
- {type == "problem" && } - {type == "question" && ( - - )} - {type == "purchase" && ( - - )} - {type == "task" && } -
- - )} + {renderChat()}
@@ -302,16 +333,16 @@ export default function Create() { {submitting ? "Submitting..." : success - ? "Thank you for creating a ticket" - : submitError - ? "Close the error box to try again" - : !isValidEmailAddress(email) - ? "Enter Valid Email Address above" - : !subject - ? "Enter Subject above" - : (body ?? "").length < MIN_BODY_LENGTH - ? `Describe your ${type} in detail above` - : "Create Support Ticket"} + ? "Thank you for creating a ticket" + : submitError + ? "Close the error box to try again" + : !isValidEmailAddress(email) + ? "Enter Valid Email Address above" + : !subject + ? "Enter Subject above" + : (body ?? "").length < MIN_BODY_LENGTH + ? `Describe your ${type} in detail above` + : "Create Support Ticket"} )} {submitting && } @@ -351,11 +382,14 @@ export default function Create() { )}
-

- After submitting this, you'll receive a link, which you should save - until you receive a confirmation email. You can also{" "} - check the status of your tickets here. -

+ {type !== "chat" && ( + + After submitting this, you'll receive a link, which you should save + until you receive a confirmation email. You can also{" "} + check the status of your tickets here + . + + )}
); diff --git a/src/packages/next/lib/customize.ts b/src/packages/next/lib/customize.ts index 6636129c93..b300af3e9c 100644 --- a/src/packages/next/lib/customize.ts +++ b/src/packages/next/lib/customize.ts @@ -70,6 +70,7 @@ interface Customize extends ServerCustomize { computeServersEnabled?: boolean; // backend configured to run on external compute servers enabledPages?: EnabledPageTree; // tree structure which specifies supported routes for this install support?: string; // HTML/MD to replace the generic support pages + supportVideoCall?: string; } const CustomizeContext = createContext>({}); diff --git a/src/packages/next/lib/with-customize.ts b/src/packages/next/lib/with-customize.ts index e6fd740475..a8951f3ff2 100644 --- a/src/packages/next/lib/with-customize.ts +++ b/src/packages/next/lib/with-customize.ts @@ -105,7 +105,7 @@ export default async function withCustomize( systemActivity: true, status: customize.onCoCalcCom, termsOfService: !customize.landingPages && !!customize.termsOfServiceURL, - liveDemo: customize.isCommercial, + liveDemo: !!customize.supportVideoCall && customize.isCommercial, }; if (obj == null) { diff --git a/src/packages/next/pages/pricing/index.tsx b/src/packages/next/pages/pricing/index.tsx index 45a09d1203..1a50cb5e4d 100644 --- a/src/packages/next/pages/pricing/index.tsx +++ b/src/packages/next/pages/pricing/index.tsx @@ -3,6 +3,8 @@ * License: MS-RSL – see LICENSE.md for details */ +import { Layout } from "antd"; + import Footer from "components/landing/footer"; import Head from "components/landing/head"; import Header from "components/landing/header"; @@ -10,7 +12,6 @@ import IndexList, { DataSource } from "components/landing/index-list"; import A from "components/misc/A"; import { Customize } from "lib/customize"; import withCustomize from "lib/with-customize"; -import { Layout } from "antd"; const dataSource: DataSource = [ { diff --git a/src/packages/next/pages/support/index.tsx b/src/packages/next/pages/support/index.tsx index 6f45351818..93afbd9aa2 100644 --- a/src/packages/next/pages/support/index.tsx +++ b/src/packages/next/pages/support/index.tsx @@ -3,15 +3,15 @@ import { Col, Layout } from "antd"; import Footer from "components/landing/footer"; import Head from "components/landing/head"; import Header from "components/landing/header"; -import A from "components/misc/A"; -import ChatGPTHelp from "components/openai/chatgpt-help"; -import { Customize } from "lib/customize"; -import withCustomize from "lib/with-customize"; -import { VideoItem } from "components/videos"; import IndexList, { DataSource } from "components/landing/index-list"; +import SocialMediaIconList from "components/landing/social-media-icon-list"; import { Title } from "components/misc"; +import A from "components/misc/A"; import SanitizedMarkdown from "components/misc/sanitized-markdown"; -import SocialMediaIconList from "components/landing/social-media-icon-list"; +import ChatGPTHelp from "components/openai/chatgpt-help"; +import { VideoItem } from "components/videos"; +import { Customize, type CustomizeType } from "lib/customize"; +import withCustomize from "lib/with-customize"; const dataSource = [ { @@ -19,17 +19,23 @@ const dataSource = [ title: "Create a New Support Ticket", logo: "medkit", hide: (customize) => !customize.zendesk, - description: ( + description: ({ supportVideoCall }: CustomizeType) => ( <> If you are having any trouble or just have a question,{" "} create a support ticket{" "} - or{" "} - - book a video chat - - . You do NOT have to be a paying customer to open a ticket. + {supportVideoCall ? ( + <> + or{" "} + + book a video chat + + + ) : ( + "" + )} + . You do NOT have to be a paying customer to contact us! supportVideoCall, title: "Book a Video Chat", - logo: "video", - description: ( + logo: "video-camera", + description: ({ supportVideoCall }: CustomizeType) => ( <> Book a{" "} - + video chat . @@ -113,16 +119,17 @@ const dataSource = [ }, { landingPages: true, - link: "https://calendly.com/cocalc/discovery", + link: ({ supportVideoCall }: CustomizeType) => supportVideoCall, title: "Request a Live Demo!", logo: "video-camera", - hide: (customize) => !customize.isCommercial, - description: ( + hide: ({ supportVideoCall, isCommercial }: CustomizeType) => + !isCommercial || !supportVideoCall, + description: ({ supportVideoCall }: CustomizeType) => ( <> If you're seriously considering using CoCalc to teach a course, but aren't sure of some of the details and really need to just{" "} talk to a person,{" "} - + fill out this form and request a live video chat with us . We love chatting (in English, German and Russian), and will hopefully @@ -130,7 +137,7 @@ const dataSource = [ ), }, -] as DataSource; +] as const satisfies DataSource; export default function Preferences({ customize }) { const { support, onCoCalcCom } = customize; diff --git a/src/packages/util/db-schema/server-settings.ts b/src/packages/util/db-schema/server-settings.ts index a4480c0b1c..088cfb63e3 100644 --- a/src/packages/util/db-schema/server-settings.ts +++ b/src/packages/util/db-schema/server-settings.ts @@ -128,4 +128,5 @@ export interface Customize { cloudFilesystemsEnabled?: boolean; githubProjectId?: string; support?: string; + supportVideoCall?: string; } diff --git a/src/packages/util/db-schema/site-defaults.ts b/src/packages/util/db-schema/site-defaults.ts index b233259fc4..3578fecdd3 100644 --- a/src/packages/util/db-schema/site-defaults.ts +++ b/src/packages/util/db-schema/site-defaults.ts @@ -62,6 +62,7 @@ export type SiteSettingsKeys = | "imprint" | "policies" | "support" + | "support_video_call" | "openai_enabled" | "google_vertexai_enabled" | "mistral_enabled" @@ -522,6 +523,14 @@ export const site_settings_conf: SiteSettings = { multiline: 5, tags: ["Theme"], }, + support_video_call: { + name: "Video Call for Support", + desc: "Link to a form to book a video call.", + default: "https://calendly.com/cocalc/discovery?back=1", + clearable: true, + show: (conf) => show_theming_vars(conf) && only_cocalc_com(conf), + tags: ["Theme"], + }, // ============== END THEMING ============ versions: {