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/next/pages/redeem.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2023 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { useState } from "react";
7
import Footer from "components/landing/footer";
8
import Header from "components/landing/header";
9
import Head from "components/landing/head";
10
import { Alert, Button, Card, Divider, Input, Layout, Space } from "antd";
11
import withCustomize from "lib/with-customize";
12
import { Customize } from "lib/customize";
13
import { Icon } from "@cocalc/frontend/components/icon";
14
import A from "components/misc/A";
15
import InPlaceSignInOrUp from "components/auth/in-place-sign-in-or-up";
16
import useProfile from "lib/hooks/profile";
17
import { useRouter } from "next/router";
18
import apiPost from "lib/api/post";
19
import useIsMounted from "lib/hooks/mounted";
20
import Loading from "components/share/loading";
21
import Project from "components/project/link";
22
import License from "components/licenses/license";
23
import type { CreatedItem } from "@cocalc/server/vouchers/redeem";
24
import { currency } from "@cocalc/util/misc";
25
26
type State = "input" | "redeeming" | "redeemed";
27
28
interface Props {
29
customize;
30
id?: string;
31
}
32
33
export default function Redeem({ customize, id }: Props) {
34
const isMounted = useIsMounted();
35
const [code, setCode] = useState<string>(id ?? "");
36
const [error, setError] = useState<string>("");
37
const [state, setState] = useState<State>("input");
38
const profile = useProfile({ noCache: true });
39
const [signedIn, setSignedIn] = useState<boolean>(!!profile?.account_id);
40
const router = useRouter();
41
const [createdItems, setCreatedItems] = useState<CreatedItem[] | null>(null);
42
43
// optional project_id to automatically apply all the licenses we get on redeeming the voucher
44
const { project_id } = router.query;
45
46
async function redeemCode() {
47
try {
48
setError("");
49
setState("redeeming");
50
// This api call tells the backend, "create requested vouchers from everything in my
51
// shopping cart that is not a subscription."
52
const createdItems = await apiPost("/vouchers/redeem", {
53
code: code.trim(),
54
project_id,
55
});
56
if (!isMounted.current) return;
57
setCreatedItems(createdItems);
58
// success!
59
setState("redeemed");
60
} catch (err) {
61
// The redeem failed.
62
setError(err.message);
63
setState("input"); // back to input mode
64
} finally {
65
if (!isMounted.current) return;
66
}
67
}
68
69
return (
70
<Customize value={customize}>
71
<Head title="Redeem Voucher" />
72
<Layout>
73
<Header />
74
<Layout.Content
75
style={{
76
backgroundColor: "white",
77
}}
78
>
79
<div
80
style={{
81
width: "100%",
82
margin: "10vh 0",
83
display: "flex",
84
justifyContent: "center",
85
}}
86
>
87
{profile == null && <Loading />}
88
{profile != null && !profile.account_id && !signedIn && (
89
<Card>
90
<div style={{ fontSize: "75px", textAlign: "center" }} >
91
<Icon name="gift2"/>
92
</div>
93
<InPlaceSignInOrUp
94
title="Redeem Voucher"
95
why="to redeem a voucher"
96
style={{ width: "450px" }}
97
onSuccess={() => {
98
router.push("/redeem");
99
setSignedIn(true);
100
}}
101
/>
102
</Card>
103
)}
104
105
{(profile?.account_id || signedIn) && (
106
<Card style={{ background: "#fafafa" }}>
107
<Space direction="vertical" align="center">
108
<A href="/vouchers">
109
<Icon name="gift2" style={{ fontSize: "75px" }} />
110
</A>
111
<h1>Enter Voucher Code</h1>
112
<Input
113
disabled={state != "input"}
114
allowClear
115
autoFocus
116
size="large"
117
value={code}
118
onChange={(e) => {
119
setCode(e.target.value);
120
setError("");
121
}}
122
onPressEnter={redeemCode}
123
style={{ width: "300px", marginBottom: "15px" }}
124
/>
125
{error && (
126
<Alert
127
type="error"
128
message={"Error"}
129
description={error}
130
showIcon
131
style={{ width: "100%", marginBottom: "30px" }}
132
closable
133
onClose={() => setError("")}
134
/>
135
)}
136
{state != "redeemed" ? (
137
<Button
138
disabled={code.length < 8 || state != "input" || !!error}
139
size="large"
140
type="primary"
141
onClick={redeemCode}
142
>
143
{state == "input" && <>Redeem</>}
144
{state == "redeeming" && (
145
<Loading delay={0}>Redeeming...</Loading>
146
)}
147
</Button>
148
) : (
149
<Alert
150
showIcon
151
message={
152
"Success! You redeemed the voucher, which added the following to your account:"
153
}
154
type="success"
155
description={
156
<DisplayCreatedItems
157
createdItems={createdItems}
158
project_id={project_id}
159
/>
160
}
161
/>
162
)}
163
{project_id && (
164
<Alert
165
showIcon
166
style={{ marginTop: "30px" }}
167
type={
168
{
169
input: "info",
170
redeeming: "warning",
171
redeemed: "success",
172
}[state] as "info" | "warning" | "success"
173
}
174
message={
175
<div style={{ maxWidth: "340px" }}>
176
{state == "input" && (
177
<>
178
The license provided by this voucher will be
179
automatically applied to your project{" "}
180
<Project project_id={project_id} />.
181
</>
182
)}
183
{state == "redeeming" && (
184
<>
185
Redeeming the voucher and applying the license it
186
to your project{" "}
187
<Project project_id={project_id} />
188
...
189
</>
190
)}
191
{state == "redeemed" && createdItems != null && (
192
<DisplayCreatedItems
193
createdItems={createdItems}
194
project_id={project_id}
195
/>
196
)}
197
</div>
198
}
199
/>
200
)}
201
{state == "redeemed" && (
202
<div style={{ textAlign: "center", marginTop: "15px" }}>
203
<Button
204
onClick={() => {
205
setState("input");
206
setCode("");
207
setError("");
208
setCreatedItems(null);
209
}}
210
>
211
Redeem Another Voucher
212
</Button>
213
</div>
214
)}
215
<Divider orientation="left" style={{ width: "400px" }}>
216
<A href="https://doc.cocalc.com/vouchers.html">
217
<Icon name="medkit" /> Vouchers
218
</A>
219
</Divider>
220
<div
221
style={{
222
color: "#666",
223
maxWidth: "450px",
224
}}
225
>
226
<p>
227
When you redeem a voucher code,{" "}
228
<A href="/settings/purchases" external>
229
money
230
</A>{" "}
231
or{" "}
232
<A href="/settings/licenses" external>
233
licenses
234
</A>{" "}
235
will be added to your account
236
{profile?.email_address != null ? (
237
<A href="/config/account/email">{` ${profile?.email_address}`}</A>
238
) : (
239
""
240
)}
241
.
242
</p>
243
<p>
244
Once you redeem a voucher code, you can use the
245
corresponding{" "}
246
<A href="/settings/purchases" external>
247
money
248
</A>{" "}
249
to make purchases, or the{" "}
250
<A href="/settings/licenses" external>
251
licenses
252
</A>{" "}
253
to{" "}
254
<A href="https://doc.cocalc.com/add-lic-project.html">
255
upgrade your projects.
256
</A>{" "}
257
If a license doesn't fit your needs, you can{" "}
258
<A href="/settings/licenses" external>
259
easily edit it here
260
</A>{" "}
261
including receiving a prorated refund so you can buy
262
something else, or paying a little more for a more
263
powerful license.
264
</p>
265
<p>
266
You can browse{" "}
267
<A href="/vouchers/redeemed">
268
all vouchers you have already redeemed.
269
</A>{" "}
270
If in a project's settings you click "Redeem Voucher" and
271
enter a voucher code you already redeemed, then the
272
corresponding licenses will get added to that project.
273
</p>
274
<p>
275
If you have any questions,{" "}
276
<A href="/support">contact support</A> and{" "}
277
<A href="https://doc.cocalc.com/vouchers.html">
278
read the documentation
279
</A>
280
.
281
</p>
282
283
<div style={{ textAlign: "center" }}>
284
<A href="/vouchers">
285
<b>The Voucher Center</b>
286
</A>
287
</div>
288
</div>
289
</Space>
290
</Card>
291
)}
292
</div>
293
<Footer />
294
</Layout.Content>{" "}
295
</Layout>
296
</Customize>
297
);
298
}
299
300
function DisplayCreatedItems({ createdItems, project_id }) {
301
if (createdItems == null) {
302
return null;
303
}
304
return (
305
<ol>
306
{createdItems.map((item, n) => (
307
<DisplayCreatedItem item={item} project_id={project_id} key={n} />
308
))}
309
</ol>
310
);
311
}
312
313
function DisplayCreatedItem({ item, project_id }) {
314
if (item.type == "cash") {
315
return (
316
<li>
317
{currency(item.amount)} was credited{" "}
318
<A href="/settings/purchases" external>
319
to your account
320
</A>{" "}
321
(transaction id: {item.purchase_id})
322
</li>
323
);
324
} else if (item.type == "license") {
325
return (
326
<li>
327
The following license <License license_id={item.license_id} /> was added{" "}
328
<A href="/settings/licenses" external>
329
to your licenses
330
</A>
331
.
332
{!!project_id && (
333
<>
334
{" "}
335
This license was applied to the project{" "}
336
<Project project_id={project_id} />.
337
</>
338
)}
339
</li>
340
);
341
} else {
342
return (
343
<li>
344
<pre>{JSON.stringify(item)}</pre>
345
</li>
346
);
347
}
348
}
349
350
export async function getServerSideProps(context) {
351
return await withCustomize({ context });
352
}
353
354