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