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/usage-and-duration.tsx
Views: 687
/*1* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { isAcademic } from "@cocalc/util/misc";6import { Subscription } from "@cocalc/util/licenses/purchase/types";7import { COSTS } from "@cocalc/util/licenses/purchase/consts";8import { Divider, Form, Input, Radio, Space } from "antd";9import DateRange from "components/misc/date-range";10import { ReactNode } from "react";11import useProfile from "lib/hooks/profile";1213interface Props {14showExplanations?: boolean;15form: any;16onChange: () => void;17disabled?: boolean;18showUsage?: boolean;19duration?: "all" | "subscriptions" | "monthly" | "yearly" | "range";20discount?: boolean;21extraDuration?: ReactNode;22}2324function getTimezoneFromDate(25date: Date,26format: "long" | "short" = "long",27): string {28return (29Intl.DateTimeFormat(undefined, {30timeZoneName: format,31})32.formatToParts(date)33.find((x) => x.type === "timeZoneName")?.value || ""34);35}3637export function UsageAndDuration(props: Props) {38const {39showExplanations = false,40form,41onChange,42disabled = false,43showUsage = true,44duration = "all",45discount = true,46extraDuration,47} = props;4849const profile = useProfile();5051function renderUsage() {52if (!showUsage) return;53return (54<Form.Item55name="user"56initialValue={57isAcademic(profile?.email_address) ? "academic" : "business"58}59label={"Usage"}60extra={61showExplanations ? (62<>63Will this license be used for academic or commercial purposes?64Academic users receive a 40% discount off the standard price.65</>66) : undefined67}68>69<Radio.Group disabled={disabled}>70<Space direction="vertical" style={{ margin: "5px 0" }}>71<Radio value={"business"}>Business - for commercial purposes</Radio>72<Radio value={"academic"}>73Academic - students, teachers, academic researchers, non-profit74organizations and hobbyists (40% discount)75</Radio>76</Space>{" "}77</Radio.Group>78</Form.Item>79);80}8182function renderRangeSelector(getFieldValue) {83const period = getFieldValue("period");84if (period !== "range") return;85const range = getFieldValue("range");86let invalidRange = range?.[0] == null || range?.[1] == null;87let suffix;88try {89if (!invalidRange) {90// always make them actual dates. See91// https://github.com/sagemathinc/cocalc/issues/717392// where this caused a crash when parsing the URL.93range[0] = new Date(range[0]);94range[1] = new Date(range[1]);95}96suffix =97range && range[0] && `(midnight to 11:59pm, ${getTimezoneFromDate(range[0], "long")})`;98} catch (err) {99invalidRange = true;100console.warn(`WARNING: issue parsing date ${range[0]}`);101suffix = undefined;102}103return (104<Form.Item105label="License Term"106name="range"107rules={[{ required: true }]}108help={invalidRange ? "Please enter a valid license range." : ""}109validateStatus={invalidRange ? "error" : "success"}110style={{ paddingBottom: "30px" }}111>112<DateRange113disabled={disabled}114noPast115maxDaysInFuture={365 * 4}116style={{ marginTop: "5px" }}117initialValues={range}118onChange={(range) => {119form.setFieldsValue({ range });120onChange();121}}122suffix={suffix}123/>124</Form.Item>125);126}127128function renderRange() {129return (130<Form.Item131noStyle132shouldUpdate={(prevValues, currentValues) =>133prevValues.period !== currentValues.period134}135>136{({ getFieldValue }) => renderRangeSelector(getFieldValue)}137</Form.Item>138);139}140141function renderSubsDiscount(duration: Subscription) {142if (!discount) return;143const pct = Math.round(100 * (1 - COSTS.sub_discount[duration]));144return <b> (discount {pct}%)</b>;145}146147function renderSubsOptions() {148if (duration === "all" || duration !== "range") {149return (150<>151{duration !== "yearly" && (152<Radio value={"monthly"}>153Monthly Subscription {renderSubsDiscount("monthly")}154</Radio>155)}156{duration !== "monthly" && (157<Radio value={"yearly"}>158Yearly Subscription {renderSubsDiscount("yearly")}159</Radio>160)}161</>162);163}164}165166function renderRangeOption() {167if (duration === "all" || duration === "range") {168return <Radio value={"range"}>Specific Start and End Dates</Radio>;169}170}171172function renderDurationExplanation() {173if (extraDuration) {174return extraDuration;175}176if (!showExplanations || !discount) return;177return (178<>179You can buy a license either via a subscription or a single purchase for180specific dates. Once you purchase a license,{" "}181<b>you can always edit it later, or cancel it for a prorated refund</b>{" "}182as credit that you can use to purchase something else. Subscriptions183will be cancelled at the end of the paid for period.{" "}184{duration == "range" && (185<i>186Licenses start and end at the indicated times in your local187timezone.188</i>189)}190</>191);192}193194function renderDuration() {195const init = duration === "range" ? "range" : "monthly";196return (197<>198<Form.Item name="range" hidden={true}>199<Input />200</Form.Item>201<Form.Item202name="period"203initialValue={init}204label="Period"205extra={renderDurationExplanation()}206>207<Radio.Group disabled={disabled}>208<Space direction="vertical" style={{ margin: "5px 0" }}>209{renderSubsOptions()}210{renderRangeOption()}211</Space>212</Radio.Group>213</Form.Item>214215{renderRange()}216</>217);218}219220return (221<>222<Divider plain>{showUsage ? "Usage and " : ""}Duration</Divider>223{renderUsage()}224{renderDuration()}225</>226);227}228229230