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. Commercial Alternative to JupyterHub.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/components/store/processing.tsx
Views: 791
1
/*
2
Page that you show user after they start a purchase and are waiting
3
for the payment to be completed and items to be allocated.
4
*/
5
6
import A from "components/misc/A";
7
import ShowError from "@cocalc/frontend/components/error";
8
import { Alert, Button, Divider, Spin, Table } from "antd";
9
import Loading from "components/share/loading";
10
import useIsMounted from "lib/hooks/mounted";
11
import { useEffect, useRef, useState } from "react";
12
import { Icon } from "@cocalc/frontend/components/icon";
13
import { type CheckoutParams } from "@cocalc/server/purchases/shopping-cart-checkout";
14
import Payments from "@cocalc/frontend/purchases/payments";
15
import { getColumns } from "./checkout";
16
import { getShoppingCartCheckoutParams } from "@cocalc/frontend/purchases/api";
17
import { SHOPPING_CART_CHECKOUT } from "@cocalc/util/db-schema/purchases";
18
import { useRouter } from "next/router";
19
20
export default function Processing() {
21
const router = useRouter();
22
const [finished, setFinished] = useState<boolean>(false);
23
const [error, setError] = useState<string>("");
24
const [loading, setLoading] = useState<boolean>(false);
25
const [params, setParams] = useState<CheckoutParams | null>(null);
26
const refreshPaymentsRef = useRef<any>(null);
27
const numPaymentsRef = useRef<number | null>(null);
28
const updateParams = async () => {
29
try {
30
setError("");
31
setLoading(true);
32
// Get what has NOT been processed.
33
const params = await getShoppingCartCheckoutParams({
34
processing: true,
35
});
36
setParams(params);
37
} catch (err) {
38
setError(`${err}`);
39
} finally {
40
setLoading(false);
41
}
42
};
43
44
useEffect(() => {
45
if (
46
params?.cart != null &&
47
params.cart.length == 0 &&
48
numPaymentsRef.current === 0
49
) {
50
setFinished(true);
51
}
52
}, [params]);
53
54
const lastRefreshRef = useRef<number>(0);
55
const refreshRef = useRef<Function>(() => {});
56
refreshRef.current = async () => {
57
const now = Date.now();
58
if (now - lastRefreshRef.current < 3000) {
59
return;
60
}
61
lastRefreshRef.current = now;
62
await updateParams();
63
await refreshPaymentsRef.current?.();
64
};
65
66
// exponential backoff auto-refresh
67
const isMounted = useIsMounted();
68
const timeoutRef = useRef<any>(null);
69
useEffect(() => {
70
if (finished) {
71
// nothing left to do
72
return;
73
}
74
let delay = 5000;
75
const f = () => {
76
if (!isMounted.current) {
77
return;
78
}
79
delay = Math.min(5 * 60 * 1000, 1.3 * delay);
80
timeoutRef.current = setTimeout(f, delay);
81
refreshRef.current();
82
};
83
f();
84
85
return () => {
86
if (timeoutRef.current) {
87
clearTimeout(timeoutRef.current);
88
timeoutRef.current = null;
89
}
90
};
91
}, [finished]);
92
93
function renderBody() {
94
if (error) {
95
return <ShowError error={error} setError={setError} />;
96
}
97
if (finished) {
98
return (
99
<div>
100
<Alert
101
type="success"
102
showIcon
103
style={{ margin: "30px auto", maxWidth: "700px" }}
104
message="Success"
105
description=<>
106
Congratulations, all your purchases have been processed and are
107
ready to use!
108
<div style={{ textAlign: "center", marginTop: "30px" }}>
109
<Button
110
size="large"
111
type="primary"
112
onClick={() => {
113
router.push("/store/congrats");
114
}}
115
>
116
Congrats! View Your Items...
117
</Button>
118
</div>
119
</>
120
/>
121
122
<Payments
123
unfinished
124
canceled
125
purpose={SHOPPING_CART_CHECKOUT}
126
refresh={() => {
127
refreshRef.current();
128
}}
129
numPaymentsRef={numPaymentsRef}
130
refreshPaymentsRef={refreshPaymentsRef}
131
/>
132
</div>
133
);
134
}
135
136
if (params?.cart == null) {
137
return null;
138
}
139
140
const done = !numPaymentsRef.current || params.cart.length == 0;
141
142
return (
143
<>
144
{!done && (
145
<Alert
146
type="warning"
147
showIcon
148
style={{ margin: "30px auto", maxWidth: "700px" }}
149
message="Status"
150
description=<>
151
Your items will be added to your account when the outstanding
152
payment listed below goes through. You can update any payment
153
configuration or cancel an unfinished payment below.
154
</>
155
/>
156
)}
157
158
{done && (
159
<Alert
160
type="success"
161
showIcon
162
style={{ margin: "30px auto", maxWidth: "700px" }}
163
message="Thank you"
164
description=<>
165
Your items should be allocated soon{" "}
166
<A href="/store/congrats">(check the Congrats tab)</A>, or in case
167
you canceled your payment, put back in your shopping cart.
168
</>
169
/>
170
)}
171
172
<Payments
173
unfinished
174
canceled
175
purpose={SHOPPING_CART_CHECKOUT}
176
refresh={() => {
177
refreshRef.current();
178
}}
179
numPaymentsRef={numPaymentsRef}
180
refreshPaymentsRef={refreshPaymentsRef}
181
/>
182
183
<Divider orientation="left" style={{ marginTop: "30px" }}>
184
Your Items
185
</Divider>
186
{params != null && (
187
<Table
188
showHeader={false}
189
columns={getColumns()}
190
dataSource={params?.cart}
191
rowKey={"id"}
192
pagination={{ hideOnSinglePage: true }}
193
/>
194
)}
195
</>
196
);
197
}
198
199
return (
200
<div>
201
<Button
202
style={{ float: "right" }}
203
disabled={loading || finished}
204
onClick={() => {
205
refreshRef.current();
206
}}
207
>
208
Check Order Status {loading && <Spin />}
209
</Button>
210
<h3>
211
<Icon name="run" /> Order Processing
212
</h3>
213
{loading && <Loading large center />}
214
{renderBody()}
215
</div>
216
);
217
}
218
219