Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/components/account/navtab.tsx
6014 views
1
/*
2
* This file is part of CoCalc: Copyright © 2021 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/* The "Account" navigation tab in the bar at the top. */
7
8
import type { MenuProps } from "antd";
9
import { Dropdown } from "antd";
10
import { join } from "path";
11
import { CSSProperties } from "react";
12
13
import { Icon } from "@cocalc/frontend/components/icon";
14
import {
15
type PreferencesSubTabType,
16
type SettingsPageType,
17
} from "@cocalc/util/types/settings";
18
import Avatar from "components/account/avatar";
19
import {
20
menuGroup,
21
menuItem,
22
MenuItem,
23
MenuItems,
24
} from "components/antd-menu-items";
25
import A from "components/misc/A";
26
import apiPost from "lib/api/post";
27
import basePath from "lib/base-path";
28
import { useCustomize } from "lib/customize";
29
import useProfile from "lib/hooks/profile";
30
import { useRouter } from "next/router";
31
32
const DIVIDER = {
33
type: "divider",
34
} as const;
35
36
// Type-safe settings link helper
37
type SettingsLink =
38
| `/settings/${SettingsPageType}`
39
| `/settings/preferences/${PreferencesSubTabType}`;
40
41
// Helper function to create type-safe settings links with basePath prefix
42
function createSettingsLink(path: SettingsLink): string {
43
return join(basePath, path);
44
}
45
46
interface Props {
47
style: CSSProperties;
48
}
49
50
// We make this menu fixed width in all cases, since otherwise the entire top navbar
51
// would flicker when profile isn't initially defined. See
52
// https://github.com/sagemathinc/cocalc/issues/6504
53
54
const WIDTH = "125px";
55
56
export default function AccountNavTab({ style }: Props) {
57
const router = useRouter();
58
const { isCommercial, shareServer, siteName, sshGateway } = useCustomize();
59
const profile = useProfile();
60
if (!profile) {
61
return (
62
<div
63
style={{
64
cursor: "pointer",
65
...style,
66
width: WIDTH,
67
}}
68
>
69
Account
70
</div>
71
);
72
}
73
74
const { first_name, last_name, name, account_id, is_admin, is_anonymous } =
75
profile;
76
77
const profile_url = name ? `/${name}` : `/share/accounts/${account_id}`;
78
79
const signedIn = menuItem(
80
"signed-in",
81
<A href={is_anonymous ? "/config/search/input" : profile_url}>
82
Signed into {siteName} as
83
<br />
84
<b>
85
{first_name} {last_name}
86
{name ? ` (@${name})` : ""}
87
</b>
88
</A>,
89
);
90
91
const docs = menuItem(
92
"docs",
93
<A href="https://doc.cocalc.com" external>
94
Documentation
95
</A>,
96
"book",
97
);
98
99
const configuration = menuGroup(
100
"configuration",
101
<A href={createSettingsLink("/settings/profile")}>
102
<span style={{ color: "#a4acb3" }}>
103
<Icon name="wrench" /> Account
104
</span>
105
</A>,
106
[
107
menuItem(
108
"profile",
109
<A href={createSettingsLink("/settings/profile")}>Profile</A>,
110
"address-card",
111
),
112
menuItem(
113
"settings",
114
<A href={createSettingsLink("/settings/index")}>Settings</A>,
115
"cogs",
116
),
117
menuItem(
118
"appearance",
119
<A href={createSettingsLink("/settings/preferences/appearance")}>
120
Appearance
121
</A>,
122
"highlighter",
123
),
124
menuItem(
125
"communication",
126
<A href={createSettingsLink("/settings/preferences/communication")}>
127
Communication
128
</A>,
129
"mail",
130
),
131
menuItem(
132
"keys",
133
<A href={createSettingsLink("/settings/preferences/keys")}>
134
SSH & API Keys
135
</A>,
136
"key",
137
),
138
DIVIDER,
139
menuItem(
140
"subscriptions",
141
<A href={createSettingsLink("/settings/subscriptions")}>
142
Subscriptions
143
</A>,
144
"calendar",
145
),
146
menuItem(
147
"licenses",
148
<A href={createSettingsLink("/settings/licenses")}>Licenses</A>,
149
"key",
150
),
151
menuItem(
152
"payg",
153
<A href={createSettingsLink("/settings/payg")}>Pay As You Go</A>,
154
"line-chart",
155
),
156
DIVIDER,
157
menuItem(
158
"purchases",
159
<A href={createSettingsLink("/settings/purchases")}>Purchases</A>,
160
"money-check",
161
),
162
menuItem(
163
"payments",
164
<A href={createSettingsLink("/settings/payments")}>Payments</A>,
165
"credit-card",
166
),
167
menuItem(
168
"statements",
169
<A href={createSettingsLink("/settings/statements")}>Statements</A>,
170
"calendar-week",
171
),
172
],
173
);
174
175
function profileItems() {
176
if (!profile) return [];
177
const ret: MenuItems = [];
178
ret.push(signedIn);
179
if (is_anonymous) {
180
ret.push(
181
menuItem(
182
"sign-up",
183
<A href="/config/search/input">
184
<b>Sign Up (save your work)!</b>
185
</A>,
186
"user",
187
),
188
);
189
}
190
ret.push(docs);
191
if (isCommercial) {
192
ret.push(menuItem("store", <A href="/store">Store</A>, "shopping-cart"));
193
}
194
ret.push(DIVIDER);
195
ret.push(configuration);
196
ret.push(DIVIDER);
197
return ret;
198
}
199
200
function yourPages(): MenuItem[] {
201
const yours: MenuItem[] = [];
202
yours.push(
203
menuItem(
204
"projects",
205
<a href={join(basePath, "projects")}>
206
{is_anonymous ? "Project" : "Projects"}
207
</a>,
208
"edit",
209
),
210
);
211
212
if (!is_anonymous) {
213
yours.push(
214
menuItem(
215
"messages",
216
<A href="/notifications#page=messages-inbox">Messages</A>,
217
"mail",
218
),
219
);
220
yours.push(
221
menuItem(
222
"mentions",
223
<A href="/notifications#page=unread">@-Mentions</A>,
224
"comment",
225
),
226
);
227
yours.push(
228
menuItem(
229
"support",
230
<A href={createSettingsLink("/settings/support")}>Support Tickets</A>,
231
"medkit",
232
),
233
);
234
235
if (sshGateway) {
236
yours.push(
237
menuItem(
238
"ssh",
239
<A href={createSettingsLink("/settings/preferences/keys")}>
240
SSH Keys
241
</A>,
242
"key",
243
),
244
);
245
}
246
247
if (shareServer) {
248
yours.push(
249
menuItem(
250
"shared",
251
<A
252
href={
253
profile?.name ? `/${name}` : `/share/accounts/${account_id}`
254
}
255
external
256
>
257
Shared Files
258
</A>,
259
"bullhorn",
260
),
261
);
262
263
// TODO: redundant with the above?
264
// yours.push(
265
// menuItem(
266
// "shared",
267
// <A href={createSettingsLink("/settings/public-files")}>
268
// Published Files
269
// </A>,
270
// "share-square",
271
// ),
272
// );
273
274
yours.push(
275
menuItem("stars", <A href="/stars">Starred Files</A>, "star-filled"),
276
);
277
}
278
}
279
280
return [
281
menuGroup(
282
"your",
283
<span style={{ color: "#a4acb3" }}>
284
<Icon name="user" /> Your...
285
</span>,
286
yours,
287
),
288
];
289
}
290
291
function admin(): MenuItem[] {
292
if (!is_admin) return [];
293
return [
294
DIVIDER,
295
menuItem(
296
"admin",
297
<a href={join(basePath, "admin")}>Site Administration</a>,
298
"settings",
299
),
300
];
301
}
302
303
const signout: MenuItem[] = [
304
DIVIDER,
305
menuItem(
306
"sign-out",
307
<A
308
onClick={async () => {
309
await apiPost("/accounts/sign-out", { all: false });
310
router.push("/");
311
}}
312
>
313
Sign Out
314
</A>,
315
),
316
];
317
318
const items: MenuProps["items"] = [
319
...profileItems(),
320
...yourPages(),
321
...admin(),
322
...signout,
323
];
324
325
// NOTE: we had a dark theme before for the menu, but that's deprecated from antd
326
// https://github.com/ant-design/ant-design/issues/4903
327
return (
328
<div
329
style={{
330
display: "inline-block",
331
cursor: "pointer",
332
width: WIDTH,
333
}}
334
>
335
{/* The negative margin fixes some weird behavior that stretches header. */}
336
{account_id && (
337
<>
338
<Avatar account_id={account_id} style={{ margin: "-10px 0" }} />
339
&nbsp;&nbsp;
340
</>
341
)}
342
<Dropdown menu={{ items }} trigger={["click"]}>
343
<span style={style}>Account ▼</span>
344
</Dropdown>
345
</div>
346
);
347
}
348
349