3
3
* License: MS-RSL – see LICENSE.md for details
4
4
*/
5
5
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" ;
6
12
import {
7
13
Alert ,
8
14
Button ,
@@ -16,21 +22,18 @@ import {
16
22
Tabs ,
17
23
Typography ,
18
24
} 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" ;
24
25
import PricingItem , { Line } from "components/landing/pricing-item" ;
25
26
import { CSS , Paragraph } from "components/misc" ;
26
27
import A from "components/misc/A" ;
27
28
import IntegerSlider from "components/misc/integer-slider" ;
28
29
import {
30
+ COURSE ,
29
31
PRESETS ,
30
32
PRESET_MATCH_FIELDS ,
31
33
Preset ,
32
34
PresetConfig ,
33
35
} from "./quota-config-presets" ;
36
+ import type { LicenseType } from "./types" ;
34
37
35
38
const { Text } = Typography ;
36
39
@@ -57,6 +60,7 @@ interface Props {
57
60
setPreset ?: ( preset : Preset | null ) => void ;
58
61
presetAdjusted ?: boolean ;
59
62
setPresetAdjusted ?: ( adjusted : boolean ) => void ;
63
+ type : LicenseType ;
60
64
}
61
65
62
66
export const QuotaConfig : React . FC < Props > = ( props : Props ) => {
@@ -72,6 +76,7 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
72
76
setPreset,
73
77
presetAdjusted,
74
78
setPresetAdjusted,
79
+ type,
75
80
} = props ;
76
81
77
82
const presetsRef = useRef < HTMLDivElement > ( null ) ;
@@ -107,7 +112,14 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
107
112
if ( boost ) {
108
113
return "Booster" ;
109
114
} 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
+ }
111
123
}
112
124
}
113
125
@@ -384,6 +396,62 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
384
396
) ;
385
397
}
386
398
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
+
387
455
function renderPresetsNarrow ( ) {
388
456
const p = preset != null ? PRESETS [ preset ] : undefined ;
389
457
let presetInfo : JSX . Element | undefined = undefined ;
@@ -560,38 +628,45 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
560
628
</ >
561
629
) ;
562
630
} 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
+ }
595
670
}
596
671
}
597
672
0 commit comments