Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/next/components/store/site-license.tsx
Views: 687
/*1* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Create a new site license.7*/8import { Form, Input } from "antd";9import { isEmpty } from "lodash";10import { useEffect, useRef, useState } from "react";1112import { Icon } from "@cocalc/frontend/components/icon";13import { get_local_storage } from "@cocalc/frontend/misc/local-storage";14import { CostInputPeriod } from "@cocalc/util/licenses/purchase/types";15import { computeCost } from "@cocalc/util/licenses/store/compute-cost";16import { Paragraph, Title } from "components/misc";17import A from "components/misc/A";18import Loading from "components/share/loading";19import SiteName from "components/share/site-name";20import apiPost from "lib/api/post";21import { MAX_WIDTH } from "lib/config";22import { useScrollY } from "lib/use-scroll-y";23import { useRouter } from "next/router";24import { AddBox } from "./add-box";25import { ApplyLicenseToProject } from "./apply-license-to-project";26import { InfoBar } from "./cost-info-bar";27import { IdleTimeout } from "./member-idletime";28import { QuotaConfig } from "./quota-config";29import { PRESETS, PRESET_MATCH_FIELDS, Preset } from "./quota-config-presets";30import { decodeFormValues, encodeFormValues } from "./quota-query-params";31import { Reset } from "./reset";32import { RunLimit } from "./run-limit";33import { SignInToPurchase } from "./sign-in-to-purchase";34import { TitleDescription } from "./title-description";35import { ToggleExplanations } from "./toggle-explanations";36import { UsageAndDuration } from "./usage-and-duration";3738const DEFAULT_PRESET: Preset = "standard";3940const STYLE: React.CSSProperties = {41marginTop: "15px",42maxWidth: MAX_WIDTH,43margin: "auto",44border: "1px solid #ddd",45padding: "15px",46} as const;4748interface Props {49noAccount: boolean;50}5152export default function SiteLicense(props: Props) {53const { noAccount } = props;54const router = useRouter();55const headerRef = useRef<HTMLHeadingElement>(null);5657// most likely, user will go to the cart next58useEffect(() => {59router.prefetch("/store/cart");60}, []);6162const [offsetHeader, setOffsetHeader] = useState(0);63const scrollY = useScrollY();6465useEffect(() => {66if (headerRef.current) {67setOffsetHeader(headerRef.current.offsetTop);68}69}, []);7071return (72<>73<Title level={3} ref={headerRef}>74<Icon name={"key"} style={{ marginRight: "5px" }} />{" "}75{router.query.id != null76? "Edit License in Shopping Cart"77: "Buy a License"}78</Title>79{router.query.id == null && (80<div>81<Paragraph style={{ fontSize: "12pt" }}>82<A href="https://doc.cocalc.com/licenses.html">83<SiteName /> licenses84</A>{" "}85allow you to upgrade any number of projects to run more quickly,86have network access, more disk space and memory. Licenses cover a87wide range of use cases, ranging from a single hobbyist project to88thousands of simultaneous users across a large organization.89</Paragraph>9091<Paragraph style={{ fontSize: "12pt" }}>92Create a license using the form below then add it to your{" "}93<A href="/store/cart">shopping cart</A>. If you aren't sure exactly94what to buy, you can always edit your licenses later. Subscriptions95are also flexible and can be{" "}96<A97href="https://doc.cocalc.com/account/purchases.html#recent-updates-to-subscriptions"98external99>100edited at any time.{" "}101</A>102It is also possible to{" "}103<A href="https://doc.cocalc.com/vouchers.html">create vouchers</A>{" "}104for resale or distribution.105</Paragraph>106</div>107)}108<CreateSiteLicense109showInfoBar={scrollY > offsetHeader}110noAccount={noAccount}111/>112</>113);114}115116// Note -- the back and forth between moment and Date below117// is a *workaround* because of some sort of bug in moment/antd/react.118119function CreateSiteLicense({ showInfoBar = false, noAccount = false }) {120const [cost, setCost] = useState<CostInputPeriod | undefined>(undefined);121const [loading, setLoading] = useState<boolean>(false);122const [cartError, setCartError] = useState<string>("");123const [showExplanations, setShowExplanations] = useState<boolean>(true);124const [configMode, setConfigMode] = useState<"preset" | "expert">("preset");125const [form] = Form.useForm();126const router = useRouter();127128const [preset, setPreset] = useState<Preset | null>(DEFAULT_PRESET);129const [presetAdjusted, setPresetAdjusted] = useState<boolean>(false);130131/**132* Utility function to match current license configuration to a particular preset. If none is133* found, this function returns undefined.134*/135function findPreset() {136const currentConfiguration = form.getFieldsValue(137Object.keys(PRESET_MATCH_FIELDS),138);139140let foundPreset: Preset | undefined;141142Object.keys(PRESETS).some((p) => {143const presetMatches = Object.keys(PRESET_MATCH_FIELDS).every(144(formField) =>145PRESETS[p][formField] === currentConfiguration[formField],146);147148if (presetMatches) {149foundPreset = p as Preset;150}151152return presetMatches;153});154155return foundPreset;156}157158function onLicenseChange() {159const vals = form.getFieldsValue(true);160encodeFormValues(router, vals, "regular");161setCost(computeCost(vals));162163const foundPreset = findPreset();164165if (foundPreset) {166setPresetAdjusted(false);167setPreset(foundPreset);168} else {169setPresetAdjusted(true);170}171}172173useEffect(() => {174const store_site_license_show_explanations = get_local_storage(175"store_site_license_show_explanations",176);177if (store_site_license_show_explanations != null) {178setShowExplanations(!!store_site_license_show_explanations);179}180181const { id } = router.query;182if (!noAccount && id != null) {183// editing something in the shopping cart184(async () => {185try {186setLoading(true);187const item = await apiPost("/shopping/cart/get", { id });188if (item.product == "site-license") {189form.setFieldsValue({ ...item.description, type: "regular" });190}191} catch (err) {192setCartError(err.message);193} finally {194setLoading(false);195}196onLicenseChange();197})();198} else {199const vals = decodeFormValues(router, "regular");200const dflt = PRESETS[DEFAULT_PRESET];201if (isEmpty(vals)) {202form.setFieldsValue({203...dflt,204});205} else {206// we have to make sure cpu, mem and disk are set, otherwise there is no "cost"207form.setFieldsValue({208...dflt,209...vals,210});211}212}213onLicenseChange();214}, []);215216if (loading) {217return <Loading large center />;218}219220const addBox = (221<AddBox222cost={cost}223router={router}224form={form}225cartError={cartError}226setCartError={setCartError}227noAccount={noAccount}228/>229);230231return (232<div>233<ApplyLicenseToProject router={router} />234<SignInToPurchase noAccount={noAccount} />235<InfoBar236show={showInfoBar}237cost={cost}238router={router}239form={form}240cartError={cartError}241setCartError={setCartError}242noAccount={noAccount}243/>244<Form245form={form}246style={STYLE}247name="basic"248labelCol={{ span: 3 }}249wrapperCol={{ span: 21 }}250autoComplete="off"251onValuesChange={onLicenseChange}252>253<Form.Item wrapperCol={{ offset: 0, span: 24 }}>{addBox}</Form.Item>254<ToggleExplanations255showExplanations={showExplanations}256setShowExplanations={setShowExplanations}257/>258{/* Hidden form item, used to disambiguate between boost and regular licenses */}259<Form.Item name="type" initialValue={"regular"} noStyle>260<Input type="hidden" />261</Form.Item>262<UsageAndDuration263showExplanations={showExplanations}264form={form}265onChange={onLicenseChange}266/>267<RunLimit268showExplanations={showExplanations}269form={form}270onChange={onLicenseChange}271/>272<QuotaConfig273boost={false}274form={form}275onChange={onLicenseChange}276showExplanations={showExplanations}277configMode={configMode}278setConfigMode={setConfigMode}279preset={preset}280setPreset={setPreset}281presetAdjusted={presetAdjusted}282/>283{configMode === "expert" ? (284<IdleTimeout285showExplanations={showExplanations}286form={form}287onChange={onLicenseChange}288/>289) : undefined}290<TitleDescription showExplanations={showExplanations} form={form} />291<Reset292addBox={addBox}293form={form}294onChange={onLicenseChange}295router={router}296/>297</Form>298</div>299);300}301302303