CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/billing/license-examples.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import {
7
discount_monthly_pct,
8
discount_yearly_pct,
9
MIN_QUOTE,
10
} from "@cocalc/util/licenses/purchase/consts";
11
import { Cost, User } from "@cocalc/util/licenses/purchase/types";
12
import { COLORS } from "@cocalc/util/theme";
13
import { Col, Row, Panel } from "@cocalc/frontend/antd-bootstrap";
14
import { React } from "../app-framework";
15
import { Icon, IconName, Gap } from "../components";
16
17
// This component renders 3 price examples for licensed upgrades in a row
18
19
export interface Example {
20
title: string;
21
icon: IconName;
22
user: User;
23
lines: { value: number; unit: string; resource: string }[];
24
price?: Cost;
25
price_monthly?: Cost; // optional, show monthly price
26
price_yearly?: Cost; // optional, show yearly price
27
period?: string;
28
}
29
30
interface Props {
31
// only renders exactly 3 examples
32
examples: Example[];
33
show_discount_pct: boolean;
34
}
35
36
export const LicenseExamples: React.FC<Props> = ({
37
examples,
38
show_discount_pct,
39
}: Props) => {
40
if (examples.length != 3) throw Error("I can only render exactly 3 examples");
41
42
function render_example_line({ value, unit, resource }) {
43
const value_str =
44
value == Number.POSITIVE_INFINITY ? <span>&#8734;</span> : value;
45
return (
46
<div key={value_str} style={{ marginBottom: "5px", marginLeft: "10px" }}>
47
<span style={{ fontWeight: "bold", color: "#444" }}>
48
{value_str} {unit}
49
</span>
50
<Gap />
51
<span style={{ color: COLORS.GRAY }}>{resource}</span>
52
</div>
53
);
54
}
55
56
function render_price_number(
57
usd,
58
small,
59
large,
60
emph: boolean,
61
online: boolean,
62
descr?: string,
63
) {
64
const smallpx = `${small}px`;
65
const largepx = `${large}px`;
66
const e = emph ? { fontWeight: "bold" as "bold" } : { color: COLORS.GRAY };
67
const style = { ...{ whiteSpace: "nowrap" as "nowrap" }, ...e };
68
if (!online && usd < MIN_QUOTE) {
69
return (
70
<span style={{ fontSize: largepx, color: COLORS.GRAY_L }}>N/A</span>
71
);
72
} else {
73
return (
74
<span style={style}>
75
<span style={{ fontSize: smallpx, verticalAlign: "super" }}>$</span>
76
<Gap />
77
<span style={{ fontSize: largepx }}>{usd.toFixed(2)}</span>
78
{descr && <span style={{ fontSize: smallpx }}> / {descr}</span>}
79
</span>
80
);
81
}
82
}
83
84
function render_example_price(price) {
85
return (
86
<>
87
{render_price_number(price.cost, 14, 30, false, false, "retail price")}
88
<br />
89
{render_price_number(
90
price.cost,
91
14,
92
30,
93
true,
94
true,
95
"purchased online",
96
)}
97
</>
98
);
99
}
100
101
function render_single_price({ price }: { price?: Cost }) {
102
if (price == null) return;
103
return (
104
<div style={{ textAlign: "center", marginTop: "10px" }}>
105
{render_example_price(price)}
106
</div>
107
);
108
}
109
110
function render_monthyear_price({
111
price_monthly,
112
price_yearly,
113
}: {
114
price_monthly?: Cost;
115
price_yearly?: Cost;
116
}) {
117
if (price_monthly == null || price_yearly == null) return;
118
const large = 26;
119
return (
120
<>
121
<table>
122
<tbody>
123
<tr>
124
<td></td>
125
<td>retail</td>
126
<th>online</th>
127
</tr>
128
<tr>
129
<td>monthly</td>
130
<td>
131
{render_price_number(
132
price_monthly.cost,
133
14,
134
large,
135
false,
136
false,
137
)}
138
</td>
139
<td>
140
{render_price_number(price_monthly.cost, 14, large, true, true)}
141
</td>
142
</tr>
143
<tr>
144
<th>yearly</th>
145
<td>
146
{render_price_number(
147
price_yearly.cost,
148
14,
149
large,
150
false,
151
false,
152
)}
153
</td>
154
<td>
155
{render_price_number(price_yearly.cost, 14, large, true, true)}
156
</td>
157
</tr>
158
</tbody>
159
</table>
160
</>
161
);
162
}
163
164
function render_example(example: Example) {
165
const { title, icon, lines, period } = example;
166
const header = (
167
<div style={{ paddingLeft: "10px" }}>
168
<Icon name={icon} /> <span style={{ fontWeight: "bold" }}>{title}</span>
169
{period && <> ({period})</>}
170
</div>
171
);
172
return (
173
<Col sm={4} key={title}>
174
<Panel header={header}>
175
<Gap />
176
{lines.map((line) => render_example_line(line))}
177
<Gap />
178
179
{render_single_price(example)}
180
{render_monthyear_price(example)}
181
</Panel>
182
</Col>
183
);
184
}
185
186
function render() {
187
return <Row>{examples.map((ex) => render_example(ex))}</Row>;
188
}
189
190
return (
191
<>
192
<h4>Examples</h4>
193
<p>
194
Here are three typical configurations. All parameters can be adjusted to
195
fit your needs. Listed upgrades are for each project. Exact prices may
196
vary. Below ${MIN_QUOTE} only online purchases are available.
197
{show_discount_pct && (
198
<>
199
{" "}
200
Compared to one-off purchases, the discounts are{" "}
201
{discount_monthly_pct}% for monthly and {discount_yearly_pct}% for
202
yearly subscriptions.
203
</>
204
)}
205
</p>
206
<Gap />
207
{render()}
208
<Gap />
209
</>
210
);
211
};
212
213