Skip to content

Commit cc33693

Browse files
committed
next/store: dedicaed course license purchase page in the store
1 parent 0cdad26 commit cc33693

File tree

7 files changed

+296
-112
lines changed

7 files changed

+296
-112
lines changed

src/packages/next/components/store/add-box.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { addToCart } from "./add-to-cart";
1515
import { DisplayCost } from "./site-license-cost";
1616
import { periodicCost } from "@cocalc/util/licenses/purchase/compute-cost";
1717
import { decimalDivide } from "@cocalc/util/stripe/calc";
18+
import { LicenseType } from "./types";
1819

1920
export const ADD_STYLE = {
2021
display: "inline-block",
@@ -37,6 +38,7 @@ interface Props {
3738
dedicatedItem?: boolean;
3839
disabled?: boolean;
3940
noAccount: boolean;
41+
type: LicenseType;
4042
}
4143

4244
export function AddBox({
@@ -48,6 +50,7 @@ export function AddBox({
4850
dedicatedItem = false,
4951
noAccount,
5052
disabled = false,
53+
type,
5154
}: Props) {
5255
if (cost?.input.type == "cash-voucher") {
5356
return null;
@@ -76,7 +79,8 @@ export function AddBox({
7679
}}
7780
message={
7881
<>
79-
{money(round2up(costPer))} <b>per project</b>{" "}
82+
{money(round2up(costPer))}{" "}
83+
<b>per {type === "course" ? "student" : "project"}</b>{" "}
8084
{!!cost.period && cost.period != "range" ? cost.period : ""}
8185
</>
8286
}
@@ -175,8 +179,8 @@ export function AddToCartButton({
175179
{clicked
176180
? "Moving to Cart..."
177181
: router.query.id != null
178-
? "Save Changes"
179-
: "Add to Cart"}
182+
? "Save Changes"
183+
: "Add to Cart"}
180184
{clicked && <Spin style={{ marginLeft: "15px" }} />}
181185
</Button>
182186
);

src/packages/next/components/store/menu.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import React, { useContext } from "react";
1111
import { Icon } from "@cocalc/frontend/components/icon";
1212
import { currency, round2down } from "@cocalc/util/misc";
1313
import { COLORS } from "@cocalc/util/theme";
14-
import { StoreBalanceContext } from "../../lib/balance";
14+
import { StoreBalanceContext } from "lib/balance";
1515

1616
type MenuItem = Required<MenuProps>["items"][number];
1717

@@ -25,6 +25,7 @@ const styles: { [k: string]: React.CSSProperties } = {
2525
menu: {
2626
width: "100%",
2727
height: "100%",
28+
flex: "1 1 auto",
2829
border: 0,
2930
},
3031
menuRoot: {

src/packages/next/components/store/run-limit.tsx

Lines changed: 112 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,78 +4,136 @@
44
*/
55

66
import { Divider, Form } from "antd";
7+
78
import A from "components/misc/A";
89
import IntegerSlider from "components/misc/integer-slider";
10+
import { LicenseType } from "./types";
11+
import { unreachable } from "@cocalc/util/misc";
912

1013
export const MAX_ALLOWED_RUN_LIMIT = 10000;
1114

15+
interface RunLimitProps {
16+
showExplanations: boolean;
17+
form: any;
18+
onChange: () => void;
19+
disabled?: boolean;
20+
boost?: boolean;
21+
type: LicenseType;
22+
}
23+
1224
export function RunLimit({
1325
showExplanations,
1426
form,
1527
onChange,
1628
disabled = false,
1729
boost = false,
18-
}) {
30+
type,
31+
}: RunLimitProps) {
1932
function extra() {
2033
if (!showExplanations) return;
2134

22-
return (
23-
<div style={{ marginTop: "5px" }}>
24-
{boost ? (
25-
<div style={{ fontWeight: "bold" }}>
26-
It's not necessary to match the run limit of the license you want to
27-
boost!
35+
switch (type) {
36+
case "license":
37+
return (
38+
<div style={{ marginTop: "5px" }}>
39+
{boost ? (
40+
<div style={{ fontWeight: "bold" }}>
41+
It's not necessary to match the run limit of the license you
42+
want to boost!
43+
</div>
44+
) : undefined}
45+
Simultaneously run this many projects using this license. You, and
46+
anyone you share the license code with, can apply the license to an
47+
unlimited number of projects, but it will only be used up to the run
48+
limit. When{" "}
49+
<A href="https://doc.cocalc.com/teaching-instructors.html">
50+
teaching a course
51+
</A>
52+
,{" "}
53+
<b>
54+
<i>
55+
the run limit is typically 2 more than the number of students
56+
(one for each student, one for the shared project and one for
57+
the instructor project)
58+
</i>
59+
</b>
60+
.
2861
</div>
29-
) : undefined}
30-
Simultaneously run this many projects using this license. You, and
31-
anyone you share the license code with, can apply the license to an
32-
unlimited number of projects, but it will only be used up to the run
33-
limit. When{" "}
34-
<A href="https://doc.cocalc.com/teaching-instructors.html">
35-
teaching a course
36-
</A>
37-
,{" "}
38-
<b>
39-
<i>
40-
the run limit is typically 2 more than the number of students (one
41-
for each student, one for the shared project and one for the
62+
);
63+
case "course":
64+
return (
65+
<div style={{ marginTop: "5px" }}>
66+
It's advised to select two more seatch than the number of students
67+
(one for each student, one for the shared project and one for the
4268
instructor project)
43-
</i>
44-
</b>
45-
.
46-
</div>
47-
);
69+
</div>
70+
);
71+
72+
default:
73+
unreachable(type);
74+
}
4875
}
4976

50-
return (
51-
<>
52-
<Divider plain>Simultaneous Project Upgrades</Divider>
53-
<Form.Item
54-
label="Run Limit"
55-
name="run_limit"
56-
initialValue={1}
57-
extra={extra()}
58-
>
59-
<EditRunLimit
60-
disabled={disabled}
61-
onChange={(run_limit) => {
62-
form.setFieldsValue({ run_limit });
63-
onChange();
64-
}}
65-
/>
66-
</Form.Item>
67-
</>
68-
);
77+
switch (type) {
78+
case "license":
79+
return (
80+
<>
81+
<Divider plain>Simultaneous Project Upgrades</Divider>
82+
<Form.Item
83+
label="Run Limit"
84+
name="run_limit"
85+
initialValue={1}
86+
extra={extra()}
87+
>
88+
<EditRunLimit
89+
type={type}
90+
disabled={disabled}
91+
onChange={(run_limit) => {
92+
form.setFieldsValue({ run_limit });
93+
onChange();
94+
}}
95+
/>
96+
</Form.Item>
97+
</>
98+
);
99+
100+
case "course":
101+
return (
102+
<>
103+
<Divider plain>Size of Course</Divider>
104+
<Form.Item
105+
label="Students"
106+
name="run_limit"
107+
initialValue={25}
108+
extra={extra()}
109+
>
110+
<EditRunLimit
111+
type={type}
112+
disabled={disabled}
113+
onChange={(run_limit) => {
114+
form.setFieldsValue({ run_limit });
115+
onChange();
116+
}}
117+
/>
118+
</Form.Item>
119+
</>
120+
);
121+
122+
default:
123+
unreachable(type);
124+
}
69125
}
70126

71-
export function EditRunLimit({
127+
function EditRunLimit({
72128
value,
73129
onChange,
74130
disabled,
131+
type,
75132
}: {
76-
value?;
77-
onChange?;
78-
disabled?;
133+
value?: number;
134+
onChange: (run_limit: number) => void;
135+
disabled?: boolean;
136+
type: LicenseType;
79137
}) {
80138
return (
81139
<IntegerSlider
@@ -85,8 +143,12 @@ export function EditRunLimit({
85143
max={300}
86144
maxText={MAX_ALLOWED_RUN_LIMIT}
87145
onChange={onChange}
88-
units={"projects"}
89-
presets={[1, 2, 10, 50, 100, 250, 500]}
146+
units={type === "course" ? "students" : "projects"}
147+
presets={
148+
type === "course"
149+
? [10, 25, 50, 75, 100, 125, 150, 200]
150+
: [1, 2, 10, 50, 100, 250, 500]
151+
}
90152
/>
91153
);
92154
}

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

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { RunLimit } from "./run-limit";
3333
import { SignInToPurchase } from "./sign-in-to-purchase";
3434
import { TitleDescription } from "./title-description";
3535
import { ToggleExplanations } from "./toggle-explanations";
36+
import { LicenseType } from "./types";
3637
import { UsageAndDuration } from "./usage-and-duration";
3738

3839
const DEFAULT_PRESET: Preset = "standard";
@@ -47,9 +48,11 @@ const STYLE: React.CSSProperties = {
4748

4849
interface Props {
4950
noAccount: boolean;
50-
type: "license" | "course";
51+
type: LicenseType;
5152
}
5253

54+
// depending on the type, this either purchases a license with all settings,
55+
// or a license for a course with a subset of controls.
5356
export default function SiteLicense({ noAccount, type }: Props) {
5457
const router = useRouter();
5558
const headerRef = useRef<HTMLHeadingElement>(null);
@@ -74,6 +77,8 @@ export default function SiteLicense({ noAccount, type }: Props) {
7477
<Icon name={"key"} style={{ marginRight: "5px" }} />{" "}
7578
{router.query.id != null
7679
? "Edit License in Shopping Cart"
80+
: type === "course"
81+
? "Purchase a License for a Course"
7782
: "Configure a License"}
7883
</Title>
7984
{router.query.id == null && (
@@ -106,14 +111,27 @@ export default function SiteLicense({ noAccount, type }: Props) {
106111
)}
107112
{type === "course" && (
108113
<div>
109-
<Paragraph style={{ fontSize: "12pt" }}>Course License</Paragraph>
114+
<Paragraph style={{ fontSize: "12pt" }}>
115+
When you teach your course on CoCalc, you benefit from a
116+
managed, reliable platform used by tens of thousands of students
117+
since 2013. Each student works in an isolated workspace
118+
(project), with options for group work. File-based assignments
119+
are handed out to students and collected when completed. You can
120+
easily monitor progress, review editing history, and assist
121+
students directly. For more information, please consult the{" "}
122+
<A href={"https://doc.cocalc.com/teaching-instructors.html"}>
123+
instructor guide
124+
</A>
125+
.
126+
</Paragraph>
110127
</div>
111128
)}
112129
</>
113130
)}
114131
<CreateSiteLicense
115132
showInfoBar={scrollY > offsetHeader}
116133
noAccount={noAccount}
134+
type={type}
117135
/>
118136
</>
119137
);
@@ -122,7 +140,15 @@ export default function SiteLicense({ noAccount, type }: Props) {
122140
// Note -- the back and forth between moment and Date below
123141
// is a *workaround* because of some sort of bug in moment/antd/react.
124142

125-
function CreateSiteLicense({ showInfoBar = false, noAccount = false }) {
143+
function CreateSiteLicense({
144+
showInfoBar = false,
145+
noAccount = false,
146+
type,
147+
}: {
148+
type: LicenseType;
149+
noAccount: boolean;
150+
showInfoBar: boolean;
151+
}) {
126152
const [cost, setCost] = useState<CostInputPeriod | undefined>(undefined);
127153
const [loading, setLoading] = useState<boolean>(false);
128154
const [cartError, setCartError] = useState<string>("");
@@ -163,6 +189,7 @@ function CreateSiteLicense({ showInfoBar = false, noAccount = false }) {
163189

164190
function onLicenseChange() {
165191
const vals = form.getFieldsValue(true);
192+
// console.log("form vals=", vals);
166193
encodeFormValues(router, vals, "regular");
167194
setCost(computeCost(vals));
168195

@@ -231,6 +258,7 @@ function CreateSiteLicense({ showInfoBar = false, noAccount = false }) {
231258
cartError={cartError}
232259
setCartError={setCartError}
233260
noAccount={noAccount}
261+
type={type}
234262
/>
235263
);
236264

@@ -269,8 +297,10 @@ function CreateSiteLicense({ showInfoBar = false, noAccount = false }) {
269297
showExplanations={showExplanations}
270298
form={form}
271299
onChange={onLicenseChange}
300+
type={type}
272301
/>
273302
<RunLimit
303+
type={type}
274304
showExplanations={showExplanations}
275305
form={form}
276306
onChange={onLicenseChange}

src/packages/next/components/store/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ export const StorePages = [
1111
] as const;
1212

1313
export type StorePagesTypes = (typeof StorePages)[number];
14+
15+
export type LicenseType = "license" | "course";

0 commit comments

Comments
 (0)