Skip to content

Commit b14951d

Browse files
committed
next/store/course: scaffolding for simple option selector configuration
1 parent 0d48bfc commit b14951d

File tree

3 files changed

+142
-38
lines changed

3 files changed

+142
-38
lines changed

src/packages/next/components/store/quota-config-presets.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,31 @@ export const PRESETS: PresetEntries = {
197197
member: true,
198198
},
199199
} as const;
200+
201+
export const COURSE = {
202+
standard: {
203+
icon: "line-chart",
204+
name: PRESET_STANDARD_NAME,
205+
descr: "is a good choice for most use cases in a course",
206+
expect: [
207+
"Run a couple of Jupyter Notebooks at once,",
208+
"Edit LaTeX, Markdown, R Documents, and use VS Code,",
209+
`${STANDARD_DISK} GB disk space is sufficient to store many files and small datasets.`,
210+
],
211+
note: <Paragraph type="secondary">TODO NOTE</Paragraph>,
212+
details: (
213+
<>
214+
You can run a couple of Jupyter Notebooks in a project at once,
215+
depending on the kernel and memory usage. This quota is fine for editing
216+
LaTeX documents, working with Sage Worksheets, using VS Code, and
217+
editing all other document types. Also, {STANDARD_DISK} GB of disk space
218+
is sufficient to store many files and a few small datasets.
219+
</>
220+
),
221+
cpu: STANDARD_CPU,
222+
ram: STANDARD_RAM,
223+
disk: STANDARD_DISK,
224+
uptime: "short",
225+
member: true,
226+
},
227+
} as const satisfies { [key in "standard"]: PresetConfig };

src/packages/next/components/store/quota-config.tsx

Lines changed: 113 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6+
import { useEffect, useRef, useState, type JSX } from "react";
7+
8+
import { Icon } from "@cocalc/frontend/components/icon";
9+
import { displaySiteLicense } from "@cocalc/util/consts/site-license";
10+
import { plural, unreachable } from "@cocalc/util/misc";
11+
import { BOOST, DISK_DEFAULT_GB, REGULAR } from "@cocalc/util/upgrades/consts";
612
import {
713
Alert,
814
Button,
@@ -16,21 +22,18 @@ import {
1622
Tabs,
1723
Typography,
1824
} from "antd";
19-
import { useEffect, useRef, useState, type JSX } from "react";
20-
import { Icon } from "@cocalc/frontend/components/icon";
21-
import { displaySiteLicense } from "@cocalc/util/consts/site-license";
22-
import { plural } from "@cocalc/util/misc";
23-
import { BOOST, DISK_DEFAULT_GB, REGULAR } from "@cocalc/util/upgrades/consts";
2425
import PricingItem, { Line } from "components/landing/pricing-item";
2526
import { CSS, Paragraph } from "components/misc";
2627
import A from "components/misc/A";
2728
import IntegerSlider from "components/misc/integer-slider";
2829
import {
30+
COURSE,
2931
PRESETS,
3032
PRESET_MATCH_FIELDS,
3133
Preset,
3234
PresetConfig,
3335
} from "./quota-config-presets";
36+
import type { LicenseType } from "./types";
3437

3538
const { Text } = Typography;
3639

@@ -57,6 +60,7 @@ interface Props {
5760
setPreset?: (preset: Preset | null) => void;
5861
presetAdjusted?: boolean;
5962
setPresetAdjusted?: (adjusted: boolean) => void;
63+
type: LicenseType;
6064
}
6165

6266
export const QuotaConfig: React.FC<Props> = (props: Props) => {
@@ -72,6 +76,7 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
7276
setPreset,
7377
presetAdjusted,
7478
setPresetAdjusted,
79+
type,
7580
} = props;
7681

7782
const presetsRef = useRef<HTMLDivElement>(null);
@@ -107,7 +112,14 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
107112
if (boost) {
108113
return "Booster";
109114
} else {
110-
return "Quota Upgrades";
115+
switch (type) {
116+
case "license":
117+
return "Quota Upgrades";
118+
case "course":
119+
return "Project Upgrades";
120+
default:
121+
unreachable(type);
122+
}
111123
}
112124
}
113125

@@ -384,6 +396,62 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
384396
);
385397
}
386398

399+
function renderCoursePresets() {
400+
const p = preset != null ? COURSE[preset] : undefined;
401+
let presetInfo: JSX.Element | undefined = undefined;
402+
if (p != null) {
403+
const { name, cpu, disk, ram, uptime, note } = p;
404+
const basic = (
405+
<>
406+
provides up to{" "}
407+
<Text strong>
408+
{cpu} {plural(cpu, "vCPU")}
409+
</Text>
410+
, <Text strong>{ram} GB memory</Text>, and{" "}
411+
<Text strong>{disk} GB disk space</Text> for each project.
412+
</>
413+
);
414+
const ut = (
415+
<>
416+
the project's{" "}
417+
<Text strong>idle timeout is {displaySiteLicense(uptime)}</Text>
418+
</>
419+
);
420+
presetInfo = (
421+
<Paragraph>
422+
<strong>{name}</strong> {basic} Additionally, {ut}. {note}
423+
</Paragraph>
424+
);
425+
}
426+
427+
return (
428+
<>
429+
<Form.Item label="Preset">
430+
<Radio.Group
431+
size="large"
432+
value={preset}
433+
onChange={(e) => onPresetChange(e.target.value)}
434+
>
435+
<Space direction="vertical">
436+
{(Object.keys(COURSE) as Array<Preset>).map((p) => {
437+
const { name, icon, descr } = COURSE[p];
438+
return (
439+
<Radio key={p} value={p}>
440+
<span>
441+
<Icon name={icon ?? "arrow-up"} />{" "}
442+
<strong>{name}:</strong> {descr}
443+
</span>
444+
</Radio>
445+
);
446+
})}
447+
</Space>
448+
</Radio.Group>
449+
</Form.Item>
450+
{presetInfo}
451+
</>
452+
);
453+
}
454+
387455
function renderPresetsNarrow() {
388456
const p = preset != null ? PRESETS[preset] : undefined;
389457
let presetInfo: JSX.Element | undefined = undefined;
@@ -560,38 +628,45 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
560628
</>
561629
);
562630
} else {
563-
return (
564-
<Tabs
565-
activeKey={configMode}
566-
onChange={setConfigMode}
567-
type="card"
568-
tabPosition="top"
569-
size="middle"
570-
centered={true}
571-
items={[
572-
{
573-
key: "preset",
574-
label: (
575-
<span>
576-
<Icon name="gears" style={{ marginRight: "5px" }} />
577-
Presets
578-
</span>
579-
),
580-
children: presetExtra(),
581-
},
582-
{
583-
key: "expert",
584-
label: (
585-
<span>
586-
<Icon name="wrench" style={{ marginRight: "5px" }} />
587-
{EXPERT_CONFIG}
588-
</span>
589-
),
590-
children: detailed(),
591-
},
592-
]}
593-
/>
594-
);
631+
switch (type) {
632+
case "license":
633+
return (
634+
<Tabs
635+
activeKey={configMode}
636+
onChange={setConfigMode}
637+
type="card"
638+
tabPosition="top"
639+
size="middle"
640+
centered={true}
641+
items={[
642+
{
643+
key: "preset",
644+
label: (
645+
<span>
646+
<Icon name="gears" style={{ marginRight: "5px" }} />
647+
Presets
648+
</span>
649+
),
650+
children: presetExtra(),
651+
},
652+
{
653+
key: "expert",
654+
label: (
655+
<span>
656+
<Icon name="wrench" style={{ marginRight: "5px" }} />
657+
{EXPERT_CONFIG}
658+
</span>
659+
),
660+
children: detailed(),
661+
},
662+
]}
663+
/>
664+
);
665+
case "course":
666+
return renderCoursePresets();
667+
default:
668+
unreachable(type);
669+
}
595670
}
596671
}
597672

src/packages/next/components/store/site-license.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ function CreateSiteLicense({
306306
onChange={onLicenseChange}
307307
/>
308308
<QuotaConfig
309+
type={type}
309310
boost={false}
310311
form={form}
311312
onChange={onLicenseChange}

0 commit comments

Comments
 (0)