Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/pages/redeem.tsx
1447 views
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 type { CreatedItem } from "@cocalc/server/vouchers/redeem";
22
import { currency } from "@cocalc/util/misc";
23
24
type State = "input" | "redeeming" | "redeemed";
25
26
interface Props {
27
customize;
28
id?: string;
29
}
30
31
export default function Redeem({ customize, id }: Props) {
32
const isMounted = useIsMounted();
33
const [code, setCode] = useState<string>(id ?? "");
34
const [error, setError] = useState<string>("");
35
const [state, setState] = useState<State>("input");
36
const profile = useProfile({ noCache: true });
37
const [signedIn, setSignedIn] = useState<boolean>(!!profile?.account_id);
38
const router = useRouter();
39
const [createdItems, setCreatedItems] = useState<CreatedItem[] | null>(null);
40
41
async function redeemCode() {
42
try {
43
setError("");
44
setState("redeeming");
45
// This api call tells the backend, "create requested vouchers from everything in my
46
// shopping cart that is not a subscription."
47
const v = code.split("/");
48
const c = v[v.length - 1]?.trim();
49
const createdItems = await apiPost("/vouchers/redeem", {
50
code: c,
51
});
52
if (!isMounted.current) return;
53
setCreatedItems(createdItems);
54
// success!
55
setState("redeemed");
56
} catch (err) {
57
// The redeem failed.
58
setError(err.message);
59
setState("input"); // back to input mode
60
} finally {
61
if (!isMounted.current) return;
62
}
63
}
64
65
return (
66
<Customize value={customize}>
67
<Head title="Redeem Voucher" />
68
<Layout>
69
<Header />
70
<Layout.Content
71
style={{
72
backgroundColor: "white",
73
}}
74
>
75
<div
76
style={{
77
width: "100%",
78
margin: "10vh 0",
79
display: "flex",
80
justifyContent: "center",
81
}}
82
>
83
{profile == null && <Loading />}
84
{profile != null && !profile.account_id && !signedIn && (
85
<Card>
86
<div style={{ fontSize: "75px", textAlign: "center" }}>
87
<Icon name="gift2" />
88
</div>
89
<InPlaceSignInOrUp
90
title="Redeem Voucher"
91
why="to redeem a voucher"
92
style={{ width: "450px" }}
93
onSuccess={() => {
94
router.push("/redeem");
95
setSignedIn(true);
96
}}
97
/>
98
</Card>
99
)}
100
101
{(profile?.account_id || signedIn) && (
102
<Card style={{ background: "#fafafa" }}>
103
<Space direction="vertical" align="center">
104
<A href="/vouchers">
105
<Icon name="gift2" style={{ fontSize: "75px" }} />
106
</A>
107
<h1>Enter Voucher Code</h1>
108
<Input
109
disabled={state != "input"}
110
allowClear
111
autoFocus
112
size="large"
113
value={code}
114
onChange={(e) => {
115
setCode(e.target.value);
116
setError("");
117
}}
118
onPressEnter={redeemCode}
119
style={{ width: "300px", marginBottom: "15px" }}
120
/>
121
{error && (
122
<Alert
123
type="error"
124
message={"Error"}
125
description={error}
126
showIcon
127
style={{ width: "100%", marginBottom: "30px" }}
128
closable
129
onClose={() => setError("")}
130
/>
131
)}
132
{state != "redeemed" ? (
133
<Button
134
disabled={code.length < 8 || state != "input" || !!error}
135
size="large"
136
type="primary"
137
onClick={redeemCode}
138
>
139
{state == "input" && <>Redeem</>}
140
{state == "redeeming" && (
141
<Loading delay={0}>Redeeming...</Loading>
142
)}
143
</Button>
144
) : (
145
<Alert
146
showIcon
147
message={
148
"Success! You redeemed the voucher, which added the following to your account:"
149
}
150
type="success"
151
description={
152
<DisplayCreatedItems createdItems={createdItems} />
153
}
154
/>
155
)}
156
{state == "redeemed" && (
157
<div style={{ textAlign: "center", marginTop: "15px" }}>
158
<Button
159
onClick={() => {
160
setState("input");
161
setCode("");
162
setError("");
163
setCreatedItems(null);
164
}}
165
>
166
Redeem Another Voucher
167
</Button>
168
</div>
169
)}
170
<Divider orientation="left" style={{ width: "400px" }}>
171
<A href="https://doc.cocalc.com/vouchers.html">
172
<Icon name="medkit" /> Vouchers
173
</A>
174
</Divider>
175
<div
176
style={{
177
color: "#666",
178
maxWidth: "450px",
179
}}
180
>
181
<p>
182
When you redeem a voucher code,{" "}
183
<A href="/settings/purchases" external>
184
credit
185
</A>{" "}
186
will be added to your account
187
{profile?.email_address != null ? (
188
<A href="/config/account/email">{` ${profile?.email_address}`}</A>
189
) : (
190
""
191
)}
192
.
193
</p>
194
<p>
195
Once you redeem a voucher code, you can use the
196
corresponding{" "}
197
<A href="/settings/purchases" external>
198
credit
199
</A>{" "}
200
to make purchases.
201
</p>
202
<p>
203
You can browse{" "}
204
<A href="/vouchers/redeemed">
205
all vouchers you have already redeemed.
206
</A>{" "}
207
</p>
208
<p>
209
If you have any questions,{" "}
210
<A href="/support">contact support</A> and{" "}
211
<A href="https://doc.cocalc.com/vouchers.html">
212
read the documentation
213
</A>
214
.
215
</p>
216
217
<div style={{ textAlign: "center" }}>
218
<A href="/vouchers">
219
<b>The Voucher Center</b>
220
</A>
221
</div>
222
</div>
223
</Space>
224
</Card>
225
)}
226
</div>
227
<Footer />
228
</Layout.Content>{" "}
229
</Layout>
230
</Customize>
231
);
232
}
233
234
function DisplayCreatedItems({ createdItems }) {
235
if (createdItems == null) {
236
return null;
237
}
238
return (
239
<ol>
240
{createdItems.map((item, n) => (
241
<DisplayCreatedItem item={item} key={n} />
242
))}
243
</ol>
244
);
245
}
246
247
function DisplayCreatedItem({ item }) {
248
if (item.type == "cash") {
249
return (
250
<li>
251
{currency(item.amount)} was credited{" "}
252
<A href={`/settings/purchases#id=${item.purchase_id}`} external>
253
to your account
254
</A>{" "}
255
(Id: {item.purchase_id})
256
</li>
257
);
258
} else {
259
return (
260
<li>
261
<pre>{JSON.stringify(item)}</pre>
262
</li>
263
);
264
}
265
}
266
267
export async function getServerSideProps(context) {
268
return await withCustomize({ context });
269
}
270
271