Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/components/store/add-box.tsx
1450 views
1
/*
2
* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
Add a cash voucher to your shopping cart.
8
*/
9
import { useState, type JSX } from "react";
10
import { CostInputPeriod } from "@cocalc/util/licenses/purchase/types";
11
import { round2up } from "@cocalc/util/misc";
12
import { money } from "@cocalc/util/licenses/purchase/utils";
13
import { Alert, Button, Spin } from "antd";
14
import { addToCart } from "./add-to-cart";
15
import { DisplayCost } from "./site-license-cost";
16
import { periodicCost } from "@cocalc/util/licenses/purchase/compute-cost";
17
import { decimalDivide } from "@cocalc/util/stripe/calc";
18
19
export const ADD_STYLE = {
20
display: "inline-block",
21
maxWidth: "550px",
22
minWidth: "400px",
23
background: "#fafafa",
24
border: "1px solid #ccc",
25
padding: "10px 20px",
26
borderRadius: "5px",
27
margin: "15px 0",
28
fontSize: "12pt",
29
} as const;
30
31
interface Props {
32
cost?: CostInputPeriod;
33
router;
34
form;
35
cartError: string | undefined;
36
setCartError: (error) => void;
37
dedicatedItem?: boolean;
38
disabled?: boolean;
39
noAccount: boolean;
40
}
41
42
export function AddBox({
43
cost,
44
router,
45
form,
46
cartError,
47
setCartError,
48
dedicatedItem = false,
49
noAccount,
50
disabled = false,
51
}: Props) {
52
if (cost?.input.type == "cash-voucher") {
53
return null;
54
}
55
// if any of the fields in cost that start with the string "cost" are NaN, disable submission:
56
if (
57
!cost ||
58
Object.keys(cost).some((k) => k.startsWith("cost") && isNaN(cost[k]))
59
) {
60
disabled = true;
61
}
62
63
function costPerProject() {
64
if (cost?.input.type != "quota") {
65
return;
66
}
67
if (dedicatedItem || cost.input.quantity == null) {
68
return;
69
}
70
const costPer = decimalDivide(periodicCost(cost), cost.input.quantity);
71
return (
72
<Alert
73
type="warning"
74
style={{
75
margin: "10px",
76
}}
77
message={
78
<>
79
{money(round2up(costPer))} <b>per project</b>{" "}
80
{!!cost.period && cost.period != "range" ? cost.period : ""}
81
</>
82
}
83
/>
84
);
85
}
86
87
function renderButton(): JSX.Element | null {
88
if (noAccount) return null;
89
90
return (
91
<div style={{ textAlign: "center", marginTop: "5px" }}>
92
{router.query.id != null && (
93
<Button
94
size="large"
95
style={{ marginRight: "5px" }}
96
onClick={() => router.push("/store/cart")}
97
disabled={disabled}
98
>
99
Cancel
100
</Button>
101
)}
102
<AddToCartButton
103
cartError={cartError}
104
cost={cost}
105
disabled={disabled}
106
form={form}
107
router={router}
108
setCartError={setCartError}
109
/>
110
{cartError && <Alert type="error" message={cartError} />}
111
</div>
112
);
113
}
114
115
return (
116
<div style={{ textAlign: "center" }}>
117
<div style={ADD_STYLE}>
118
{cost && <DisplayCost cost={cost} />}
119
{cost && costPerProject()}
120
{renderButton()}
121
</div>
122
</div>
123
);
124
}
125
126
interface CartButtonProps {
127
cost: CostInputPeriod | undefined;
128
router;
129
form;
130
setCartError: (error) => void;
131
disabled?: boolean;
132
cartError: string | undefined;
133
variant?: "primary" | "small";
134
}
135
136
export function AddToCartButton({
137
cost,
138
form,
139
router,
140
setCartError,
141
cartError,
142
variant = "primary",
143
disabled: disabled0,
144
}: CartButtonProps) {
145
const [clicked, setClicked] = useState<boolean>(false);
146
const disabled =
147
clicked ||
148
(disabled0 ?? false) ||
149
!!cartError ||
150
cost == null ||
151
cost.cost === 0;
152
153
return (
154
<Button
155
size={variant === "small" ? "small" : "large"}
156
type="primary"
157
htmlType="submit"
158
onClick={async () => {
159
// you can only click this add to cart button *once* -- due to slow
160
// turnaround, if we don't change this state, then the user could
161
// click multiple times and add the same item more than once, thus
162
// accidentally ending up with a "dobule purchase"
163
try {
164
setClicked(true);
165
await addToCart({ form, setCartError, router });
166
} catch (_err) {
167
// error is reported via setCartError. But also
168
// give a chance to click the button again, since item
169
// wasn't actually added.
170
setClicked(false);
171
}
172
}}
173
disabled={disabled}
174
>
175
{clicked
176
? "Moving to Cart..."
177
: router.query.id != null
178
? "Save Changes"
179
: "Add to Cart"}
180
{clicked && <Spin style={{ marginLeft: "15px" }} />}
181
</Button>
182
);
183
}
184
185