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/vouchers/admin.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 { useCallback, useMemo, 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, Checkbox, Layout, Space, Table } 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 Loading from "components/share/loading";
19
import useDatabase from "lib/hooks/database";
20
import { field_cmp } from "@cocalc/util/misc";
21
import type { Voucher } from "@cocalc/util/db-schema/vouchers";
22
import apiPost from "lib/api/post";
23
import Help from "components/vouchers/help";
24
25
const QUERY = {
26
crm_vouchers: [
27
{
28
id: null,
29
when_pay: null,
30
created: null,
31
active: null,
32
expire: null,
33
cancel_by: null,
34
title: null,
35
count: null,
36
cost: null,
37
tax: null,
38
cart: null,
39
purchased: null,
40
},
41
],
42
} as const;
43
44
import { COLUMNS as COLUMNS0 } from "./created";
45
const COLUMNS = COLUMNS0.concat([
46
{
47
title: "When Pay",
48
dataIndex: "when_pay",
49
key: "when_pay",
50
},
51
{
52
title: "Purchased",
53
dataIndex: "purchased",
54
key: "purchased",
55
render: (_, { purchased }) => (
56
<pre style={{ maxWidth: "200px", overflow: "auto" }}>
57
{JSON.stringify(purchased, undefined, 2)}
58
</pre>
59
),
60
},
61
]);
62
63
export default function Created({ customize }) {
64
const { loading, value, error, setError, query } = useDatabase(QUERY);
65
const profile = useProfile({ noCache: true });
66
const router = useRouter();
67
const [showExpiredOnly, setShowExpiredOnly] = useState<boolean>(false);
68
const [showAdminOnly, setShowAdminOnly] = useState<boolean>(false);
69
const [showPaidOnly, setShowPaidOnly] = useState<boolean>(false);
70
71
const [charging, setCharging] = useState<boolean>(false);
72
const [result, setResult] = useState<any>(null);
73
74
const doInvoiceUnpaid = useCallback(() => {
75
setCharging(true);
76
(async () => {
77
try {
78
setResult(await apiPost("/vouchers/charge-for-unpaid-vouchers"));
79
} catch (err) {
80
setError(`${err}`);
81
} finally {
82
setCharging(false);
83
query(QUERY);
84
}
85
})();
86
}, []);
87
88
const data: Voucher[] = useMemo(() => {
89
if (error) return [];
90
const cmp = field_cmp("created");
91
let v: Voucher[] = (value?.crm_vouchers ?? []).sort((a, b) => -cmp(a, b));
92
if (showExpiredOnly) {
93
const now = new Date();
94
v = v.filter((x) => new Date(x.expire) <= now);
95
}
96
if (showPaidOnly) {
97
v = v.filter((x) => x.purchased != null);
98
}
99
if (showAdminOnly) {
100
v = v.filter((x) => x.when_pay == "admin");
101
}
102
103
return v;
104
}, [
105
value,
106
showExpiredOnly,
107
showPaidOnly,
108
showAdminOnly,
109
error,
110
]);
111
112
return (
113
<Customize value={customize}>
114
<Head title="Admin: Voucher Payment Status" />
115
<Layout>
116
<Header />
117
<Layout.Content style={{ background: "white" }}>
118
{profile != null && !profile.is_admin && (
119
<div>
120
<Alert
121
showIcon
122
style={{ margin: "30px" }}
123
type="warning"
124
message={<b>This page is only for system administrators.</b>}
125
/>
126
</div>
127
)}
128
<div
129
style={{
130
width: "100%",
131
margin: "10vh 0",
132
display: "flex",
133
justifyContent: "center",
134
}}
135
>
136
{profile == null && <Loading />}
137
{profile != null && !profile.account_id && (
138
<Card>
139
<div style={{ fontSize: "75px", textAlign: "center" }}>
140
<Icon name="gift2"/>
141
</div>
142
<InPlaceSignInOrUp
143
title="Voucher Status"
144
why="as an ADMIN to see voucher payment status"
145
style={{ width: "450px" }}
146
onSuccess={() => {
147
router.reload();
148
}}
149
/>
150
</Card>
151
)}
152
{profile?.account_id && (
153
<Card style={{ background: "#fafafa" }}>
154
<Space direction="vertical" align="center">
155
<A href="/vouchers">
156
<Icon name="gift2" style={{ fontSize: "75px" }} />
157
</A>
158
<h1>
159
<Icon name="users" /> Admin -- Voucher Payment Status (
160
{data.length})
161
</h1>
162
{error && (
163
<Alert
164
type="error"
165
message={error}
166
showIcon
167
style={{ width: "100%", marginBottom: "30px" }}
168
closable
169
onClose={() => setError("")}
170
/>
171
)}
172
{loading && <Loading />}
173
{!loading && (
174
<div>
175
<Checkbox
176
disabled={charging}
177
checked={showExpiredOnly}
178
onClick={() => setShowExpiredOnly(!showExpiredOnly)}
179
>
180
Show expired only
181
</Checkbox>
182
<Checkbox
183
disabled={charging}
184
checked={showAdminOnly}
185
onClick={() => setShowAdminOnly(!showAdminOnly)}
186
>
187
Show admin only
188
</Checkbox>
189
<Checkbox
190
disabled={charging}
191
checked={showPaidOnly}
192
onClick={() => setShowPaidOnly(!showPaidOnly)}
193
>
194
Show paid only
195
</Checkbox>
196
<div style={{ maxWidth: "600px", marginTop: "15px" }}>
197
NOTE: Click the unpaid and expired checkboxes to bring
198
up the button to manually run invoicing. This is
199
temporary until we automate this later.
200
</div>
201
</div>
202
)}
203
{!loading && showExpiredOnly && (
204
<div>
205
<Button
206
style={{ marginTop: "30px" }}
207
type="primary"
208
onClick={doInvoiceUnpaid}
209
disabled={charging || data.length == 0}
210
>
211
{charging && (
212
<>
213
<Loading />{" "}
214
</>
215
)}
216
Create Invoices and Charge for the {data.length} Unpaid
217
Vouchers
218
</Button>
219
{result && (
220
<div>
221
Invoice Result:
222
<pre>{JSON.stringify(result, undefined, 2)}</pre>
223
</div>
224
)}
225
</div>
226
)}
227
{!loading && data.length > 0 && (
228
<Table
229
columns={COLUMNS}
230
dataSource={data}
231
rowKey="id"
232
pagination={{ defaultPageSize: 50 }}
233
/>
234
)}
235
{!loading && data.length == 0 && (
236
<div>There are no matching vouchers.</div>
237
)}
238
<Help />
239
</Space>
240
</Card>
241
)}
242
</div>
243
<Footer />
244
</Layout.Content>
245
</Layout>
246
</Customize>
247
);
248
}
249
250
export async function getServerSideProps(context) {
251
return await withCustomize({ context });
252
}
253
254