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/frontend/admin/users/user.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
Display of basic information about a user, with link to get more information about that user.
8
*/
9
10
import { Icon, Gap, TimeAgo } from "@cocalc/frontend/components";
11
import { Component, Rendered } from "@cocalc/frontend/app-framework";
12
import { capitalize } from "@cocalc/util/misc";
13
import { Row, Col } from "@cocalc/frontend/antd-bootstrap";
14
import { User } from "@cocalc/frontend/frame-editors/generic/client";
15
import { Projects } from "./projects";
16
import { Impersonate } from "./impersonate";
17
import { PasswordReset } from "./password-reset";
18
import { Ban } from "./ban";
19
import PayAsYouGoMinBalance from "@cocalc/frontend/frame-editors/crm-editor/users/pay-as-you-go-min-balance";
20
import { PurchasesButton } from "@cocalc/frontend/purchases/purchases";
21
import { CopyToClipBoard } from "@cocalc/frontend/components";
22
import Money from "./money";
23
24
interface State {
25
projects: boolean;
26
purchases: boolean;
27
activity: boolean;
28
impersonate: boolean;
29
password: boolean;
30
ban: boolean;
31
}
32
33
interface HeaderProps {
34
header: true;
35
first_name: string;
36
last_name: string;
37
email_address: string;
38
created: string;
39
last_active: string;
40
account_id: string;
41
banned?: undefined;
42
}
43
44
interface UserProps extends User {
45
header?: false;
46
}
47
48
type Props = HeaderProps | UserProps;
49
50
type More =
51
| "projects"
52
| "purchases"
53
| "activity"
54
| "impersonate"
55
| "password"
56
| "ban";
57
58
const MORE: More[] = [
59
"projects",
60
"purchases",
61
"activity",
62
"impersonate",
63
"password",
64
"ban",
65
];
66
67
export class UserResult extends Component<Props, State> {
68
constructor(props, state) {
69
super(props, state);
70
const x: any = {};
71
for (const name of MORE) {
72
x[name] = false;
73
}
74
this.state = x as State;
75
}
76
77
render_created(): Rendered {
78
if (!this.props.created) {
79
return <span>unknown</span>;
80
}
81
return <TimeAgo date={this.props.created} />;
82
}
83
84
render_last_active(): Rendered {
85
if (!this.props.last_active) {
86
return <span>unknown</span>;
87
}
88
return <TimeAgo date={this.props.last_active} />;
89
}
90
91
render_purchases(): Rendered {
92
if (!this.state.purchases) {
93
return;
94
}
95
return (
96
<div style={{ margin: "15px 0" }}>
97
<Money account_id={this.props.account_id} />
98
<div style={{ height: "15px" }} />
99
<PayAsYouGoMinBalance account_id={this.props.account_id} />
100
<div style={{ height: "15px" }} />
101
<PurchasesButton account_id={this.props.account_id} />
102
</div>
103
);
104
}
105
106
render_projects(): Rendered {
107
if (!this.state.projects) {
108
return;
109
}
110
return (
111
<Projects
112
account_id={this.props.account_id}
113
title={`Recently active projects that ${this.props.first_name} ${this.props.last_name} collaborates on`}
114
/>
115
);
116
}
117
118
render_impersonate(): Rendered {
119
if (!this.state.impersonate) {
120
return;
121
}
122
return (
123
<Impersonate
124
account_id={this.props.account_id}
125
first_name={this.props.first_name ?? ""}
126
last_name={this.props.last_name ?? ""}
127
/>
128
);
129
}
130
131
render_password(): Rendered {
132
if (!this.state.password) {
133
return;
134
}
135
return <PasswordReset email_address={this.props.email_address} />;
136
}
137
138
render_ban(): Rendered {
139
if (!this.state.ban) {
140
return;
141
}
142
return (
143
<Ban
144
account_id={this.props.account_id}
145
banned={this.props.banned}
146
name={`${this.props.first_name} ${this.props.last_name} ${this.props.email_address}`}
147
/>
148
);
149
}
150
151
render_caret(show: boolean): Rendered {
152
if (show) {
153
return <Icon name="caret-down" />;
154
} else {
155
return <Icon name="caret-right" />;
156
}
157
}
158
159
render_more_link(name: More): Rendered {
160
// sorry about the any below; I could NOT get typescript to work.
161
return (
162
<a
163
style={{ cursor: "pointer" }}
164
onClick={() => (this as any).setState({ [name]: !this.state[name] })}
165
>
166
{this.render_caret(this.state[name])} {capitalize(name)}
167
</a>
168
);
169
}
170
171
render_more_links(): Rendered {
172
return (
173
<div>
174
{this.render_more_link("projects")}
175
<Gap />
176
<Gap />
177
{this.render_more_link("purchases")}
178
<Gap />
179
<Gap />
180
{this.render_more_link("impersonate")}
181
<Gap />
182
<Gap />
183
{this.render_more_link("password")}
184
<Gap />
185
<Gap />
186
{this.render_more_link("ban")}
187
</div>
188
);
189
}
190
191
render_banned(): Rendered {
192
if (!this.props.banned) return;
193
return (
194
<div
195
style={{
196
fontSize: "10pt",
197
color: "white",
198
paddingLeft: "5px",
199
background: "red",
200
}}
201
>
202
BANNED
203
</div>
204
);
205
}
206
207
render_row(): Rendered {
208
return (
209
<div>
210
<Row style={{ borderTop: "1px solid #ccc" }}>
211
<Col md={1} style={{ overflow: "auto" }}>
212
{this.props.first_name}
213
</Col>
214
<Col md={1} style={{ overflow: "auto" }}>
215
{this.props.last_name}
216
</Col>
217
<Col md={2} style={{ overflow: "auto" }}>
218
{this.props.email_address}
219
</Col>
220
<Col md={2}>
221
{this.render_last_active()} ({this.render_created()})
222
</Col>
223
<Col md={4}>{this.render_more_links()}</Col>
224
<Col md={2} style={{ overflow: "auto" }}>
225
<div
226
style={{
227
paddingTop: "8px",
228
overflowX: "scroll",
229
}}
230
>
231
<CopyToClipBoard
232
before
233
value={this.props.account_id}
234
size="small"
235
/>
236
{this.render_banned()}
237
</div>
238
</Col>
239
</Row>
240
{this.render_impersonate()}
241
{this.render_password()}
242
{this.render_ban()}
243
{this.render_projects()}
244
{this.render_purchases()}
245
</div>
246
);
247
}
248
249
render_row_header(): Rendered {
250
return (
251
<div style={{ color: "#666" }}>
252
<Row>
253
<Col md={1} style={{ overflow: "auto" }}>
254
<b>{this.props.first_name}</b>
255
</Col>
256
<Col md={1} style={{ overflow: "auto" }}>
257
<b>{this.props.last_name}</b>
258
</Col>
259
<Col md={2} style={{ overflow: "auto" }}>
260
<b>{this.props.email_address}</b>
261
</Col>
262
<Col md={2}>
263
<b>
264
{this.props.last_active} ({this.props.created}){" "}
265
<Icon name="caret-down" />{" "}
266
</b>
267
</Col>
268
<Col md={4}>
269
<b>More...</b>
270
</Col>
271
<Col md={2}>
272
<b>{this.props.account_id}</b>
273
</Col>
274
</Row>
275
</div>
276
);
277
}
278
279
render(): Rendered {
280
if (this.props.header) {
281
return this.render_row_header();
282
} else {
283
return this.render_row();
284
}
285
}
286
}
287
288