Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/account/settings-index.tsx
5961 views
1
/*
2
* This file is part of CoCalc: Copyright © 2025 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
// cSpell:ignore payg
7
8
import { Card, Divider, Flex } from "antd";
9
import { defineMessages, useIntl } from "react-intl";
10
11
import { redux, useTypedRedux } from "@cocalc/frontend/app-framework";
12
import { Icon } from "@cocalc/frontend/components";
13
import AIAvatar from "@cocalc/frontend/components/ai-avatar";
14
import { cloudFilesystemsEnabled } from "@cocalc/frontend/compute";
15
import { labels } from "@cocalc/frontend/i18n";
16
import { KUCALC_COCALC_COM } from "@cocalc/util/db-schema/site-defaults";
17
import { COLORS } from "@cocalc/util/theme";
18
import {
19
VALID_PREFERENCES_SUB_TYPES,
20
type NavigatePath,
21
type PreferencesSubTabType,
22
} from "@cocalc/util/types/settings";
23
import { APPEARANCE_ICON_NAME } from "./account-preferences-appearance";
24
import { COMMUNICATION_ICON_NAME } from "./account-preferences-communication";
25
import { EDITOR_ICON_NAME } from "./account-preferences-editor";
26
import { KEYBOARD_ICON_NAME } from "./account-preferences-keyboard";
27
import { OTHER_ICON_NAME } from "./account-preferences-other";
28
import { ACCOUNT_PROFILE_ICON_NAME } from "./account-preferences-profile";
29
import { KEYS_ICON_NAME } from "./account-preferences-security";
30
31
const MESSAGES = defineMessages({
32
title: {
33
id: "account.settings.overview.title",
34
defaultMessage: "Settings Overview",
35
},
36
profile: {
37
id: "account.settings.overview.profile",
38
defaultMessage:
39
"Manage your personal information, avatar, and account details.",
40
},
41
appearance: {
42
id: "account.settings.overview.appearance",
43
defaultMessage:
44
"Customize the visual experience via color themes, {dark_mode}, language, and visual settings.",
45
},
46
editor: {
47
id: "account.settings.overview.editor",
48
defaultMessage:
49
"Customize code editor behavior, indentation, and content options.",
50
},
51
keyboard: {
52
id: "account.settings.overview.keyboard",
53
defaultMessage: "Keyboard shortcuts.",
54
},
55
ai: {
56
id: "account.settings.overview.ai",
57
defaultMessage: "Configure AI assistant settings and integrations.",
58
},
59
communication: {
60
id: "account.settings.overview.communication",
61
defaultMessage: "Notification preferences and communication settings.",
62
},
63
keys: {
64
id: "account.settings.overview.keys",
65
defaultMessage: "Manage API keys and setup SSH keys.",
66
},
67
other: {
68
id: "account.settings.overview.other",
69
defaultMessage: "Miscellaneous settings and options.",
70
},
71
billing: {
72
id: "account.settings.overview.billing",
73
defaultMessage: "Manage subscriptions, licenses, and billing information.",
74
},
75
subscriptions: {
76
id: "account.settings.overview.subscriptions",
77
defaultMessage: "View and manage your active subscriptions.",
78
},
79
licenses: {
80
id: "account.settings.overview.licenses",
81
defaultMessage: "Manage software licenses and access permissions.",
82
},
83
payg: {
84
id: "account.settings.overview.payg",
85
defaultMessage: "Configure pay-as-you-go usage and billing.",
86
},
87
upgrades: {
88
id: "account.settings.overview.upgrades",
89
defaultMessage: "Manage your legacy quota upgrades.",
90
},
91
purchases: {
92
id: "account.settings.overview.purchases",
93
defaultMessage: "View purchase history and receipts.",
94
},
95
payments: {
96
id: "account.settings.overview.payments",
97
defaultMessage: "Manage payment methods and transaction history.",
98
},
99
paymentMethods: {
100
id: "account.settings.overview.payment_methods",
101
defaultMessage: "Manage your saved payment methods or add new ones.",
102
},
103
statements: {
104
id: "account.settings.overview.statements",
105
defaultMessage: "View detailed billing statements and invoices.",
106
},
107
files: {
108
id: "account.settings.overview.files",
109
defaultMessage: "Manage published files and public sharing.",
110
},
111
cloud: {
112
id: "account.settings.overview.cloud",
113
defaultMessage: "Manage cloud file system storage.",
114
},
115
support: {
116
id: "account.settings.overview.support",
117
defaultMessage: "Access support tickets and help resources.",
118
},
119
});
120
121
const CARD_PROPS = {
122
size: "small" as const,
123
hoverable: true,
124
style: { width: 300, minWidth: 250 },
125
} as const;
126
127
// TODO: highlighting a few cards is a good idea, but better to do this later...
128
const HIGHLIGHTED_CARD_PROPS = CARD_PROPS; // = {
129
// ...CARD_PROPS,
130
// style: {
131
// ...CARD_PROPS.style,
132
// border: `2px solid ${COLORS.BLUE_LLL}`,
133
// },
134
// } as const;
135
136
const FLEX_PROPS = {
137
wrap: true as const,
138
gap: "15px",
139
style: { marginBottom: "40px" },
140
} as const;
141
142
export function SettingsOverview() {
143
const intl = useIntl();
144
const is_commercial = useTypedRedux("customize", "is_commercial");
145
const kucalc = useTypedRedux("customize", "kucalc");
146
147
function handleNavigate(path: NavigatePath) {
148
// Use the same navigation pattern as the account page
149
const segments = path.split("/").filter(Boolean);
150
if (segments[0] === "settings") {
151
if (segments[1] === "profile") {
152
redux.getActions("account").setState({
153
active_page: "profile",
154
active_sub_tab: undefined,
155
});
156
redux.getActions("account").push_state(`/profile`);
157
} else if (segments[1] === "preferences" && segments[2]) {
158
// Handle preferences sub-tabs
159
const subTab = segments[2] as PreferencesSubTabType;
160
if (VALID_PREFERENCES_SUB_TYPES.includes(subTab)) {
161
const subTabKey = `preferences-${subTab}` as const;
162
redux.getActions("account").setState({
163
active_page: "preferences",
164
active_sub_tab: subTabKey,
165
});
166
redux.getActions("account").push_state(`/preferences/${subTab}`);
167
}
168
} else {
169
// Handle other settings pages
170
redux.getActions("account").set_active_tab(segments[1]);
171
redux.getActions("account").push_state(`/${segments[1]}`);
172
}
173
}
174
}
175
176
return (
177
<div style={{ padding: "20px" }}>
178
<Flex {...FLEX_PROPS}>
179
<Card
180
{...HIGHLIGHTED_CARD_PROPS}
181
onClick={() => handleNavigate("settings/profile")}
182
>
183
<Card.Meta
184
avatar={<Icon name={ACCOUNT_PROFILE_ICON_NAME} />}
185
title={intl.formatMessage(labels.profile)}
186
description={intl.formatMessage(MESSAGES.profile)}
187
/>
188
</Card>
189
<Card
190
{...HIGHLIGHTED_CARD_PROPS}
191
onClick={() => handleNavigate("settings/preferences/appearance")}
192
>
193
<Card.Meta
194
avatar={<Icon name={APPEARANCE_ICON_NAME} />}
195
title={intl.formatMessage(labels.appearance)}
196
description={intl.formatMessage(MESSAGES.appearance, {
197
dark_mode: (
198
<span
199
style={{
200
backgroundColor: COLORS.GRAY_DD,
201
color: COLORS.GRAY_LLL,
202
paddingLeft: "3px",
203
paddingRight: "3px",
204
}}
205
>
206
dark mode
207
</span>
208
),
209
})}
210
/>
211
</Card>
212
<Card
213
{...CARD_PROPS}
214
onClick={() => handleNavigate("settings/preferences/editor")}
215
>
216
<Card.Meta
217
avatar={<Icon name={EDITOR_ICON_NAME} />}
218
title={intl.formatMessage(labels.editor)}
219
description={intl.formatMessage(MESSAGES.editor)}
220
/>
221
</Card>
222
<Card
223
{...CARD_PROPS}
224
onClick={() => handleNavigate("settings/preferences/keyboard")}
225
>
226
<Card.Meta
227
avatar={<Icon name={KEYBOARD_ICON_NAME} />}
228
title={intl.formatMessage(labels.keyboard)}
229
description={intl.formatMessage(MESSAGES.keyboard)}
230
/>
231
</Card>
232
<Card
233
{...CARD_PROPS}
234
onClick={() => handleNavigate("settings/preferences/ai")}
235
>
236
<Card.Meta
237
avatar={<AIAvatar size={24} />}
238
title={intl.formatMessage(labels.ai)}
239
description={intl.formatMessage(MESSAGES.ai)}
240
/>
241
</Card>
242
<Card
243
{...CARD_PROPS}
244
onClick={() => handleNavigate("settings/preferences/communication")}
245
>
246
<Card.Meta
247
avatar={<Icon name={COMMUNICATION_ICON_NAME} />}
248
title={intl.formatMessage(labels.communication)}
249
description={intl.formatMessage(MESSAGES.communication)}
250
/>
251
</Card>
252
<Card
253
{...CARD_PROPS}
254
onClick={() => handleNavigate("settings/preferences/keys")}
255
>
256
<Card.Meta
257
avatar={<Icon name={KEYS_ICON_NAME} />}
258
title={intl.formatMessage(labels.ssh_and_api_keys)}
259
description={intl.formatMessage(MESSAGES.keys)}
260
/>
261
</Card>
262
<Card
263
{...CARD_PROPS}
264
onClick={() => handleNavigate("settings/preferences/other")}
265
>
266
<Card.Meta
267
avatar={<Icon name={OTHER_ICON_NAME} />}
268
title={intl.formatMessage(labels.other)}
269
description={intl.formatMessage(MESSAGES.other)}
270
/>
271
</Card>
272
</Flex>
273
274
{is_commercial && (
275
<>
276
<Divider plain>
277
<Icon name="money-check" /> {intl.formatMessage(labels.billing)}
278
</Divider>
279
<Flex {...FLEX_PROPS}>
280
<Card
281
{...HIGHLIGHTED_CARD_PROPS}
282
onClick={() => handleNavigate("settings/subscriptions")}
283
>
284
<Card.Meta
285
avatar={<Icon name="calendar" />}
286
title={intl.formatMessage(labels.subscriptions)}
287
description={intl.formatMessage(MESSAGES.subscriptions)}
288
/>
289
</Card>
290
<Card
291
{...HIGHLIGHTED_CARD_PROPS}
292
onClick={() => handleNavigate("settings/licenses")}
293
>
294
<Card.Meta
295
avatar={<Icon name="key" />}
296
title={intl.formatMessage(labels.licenses)}
297
description={intl.formatMessage(MESSAGES.licenses)}
298
/>
299
</Card>
300
<Card
301
{...CARD_PROPS}
302
onClick={() => handleNavigate("settings/payg")}
303
>
304
<Card.Meta
305
avatar={<Icon name="line-chart" />}
306
title={intl.formatMessage(labels.pay_as_you_go)}
307
description={intl.formatMessage(MESSAGES.payg)}
308
/>
309
</Card>
310
{kucalc === KUCALC_COCALC_COM && (
311
<Card
312
{...CARD_PROPS}
313
onClick={() => handleNavigate("settings/upgrades")}
314
>
315
<Card.Meta
316
avatar={<Icon name="arrow-circle-up" />}
317
title={intl.formatMessage(labels.upgrades)}
318
description={intl.formatMessage(MESSAGES.upgrades)}
319
/>
320
</Card>
321
)}
322
<Card
323
{...CARD_PROPS}
324
onClick={() => handleNavigate("settings/purchases")}
325
>
326
<Card.Meta
327
avatar={<Icon name="money-check" />}
328
title={intl.formatMessage(labels.purchases)}
329
description={intl.formatMessage(MESSAGES.purchases)}
330
/>
331
</Card>
332
<Card
333
{...CARD_PROPS}
334
onClick={() => handleNavigate("settings/payments")}
335
>
336
<Card.Meta
337
avatar={<Icon name="credit-card" />}
338
title={intl.formatMessage(labels.payments)}
339
description={intl.formatMessage(MESSAGES.payments)}
340
/>
341
</Card>
342
<Card
343
{...CARD_PROPS}
344
onClick={() => handleNavigate("settings/payment-methods")}
345
>
346
<Card.Meta
347
avatar={<Icon name="credit-card" />}
348
title={intl.formatMessage(labels.payment_methods)}
349
description={intl.formatMessage(MESSAGES.paymentMethods)}
350
/>
351
</Card>
352
<Card
353
{...CARD_PROPS}
354
onClick={() => handleNavigate("settings/statements")}
355
>
356
<Card.Meta
357
avatar={<Icon name="calendar-week" />}
358
title={intl.formatMessage(labels.statements)}
359
description={intl.formatMessage(MESSAGES.statements)}
360
/>
361
</Card>
362
</Flex>
363
</>
364
)}
365
366
<Divider plain>
367
<Icon name="files" /> {intl.formatMessage(labels.files)}
368
</Divider>
369
<Flex {...FLEX_PROPS}>
370
<Card
371
{...CARD_PROPS}
372
onClick={() => handleNavigate("settings/public-files")}
373
>
374
<Card.Meta
375
avatar={<Icon name="share-square" />}
376
title={intl.formatMessage(labels.published_files)}
377
description={intl.formatMessage(MESSAGES.files)}
378
/>
379
</Card>
380
381
{cloudFilesystemsEnabled() && (
382
<Card
383
{...CARD_PROPS}
384
onClick={() => handleNavigate("settings/cloud-filesystems")}
385
>
386
<Card.Meta
387
avatar={<Icon name="server" />}
388
title={intl.formatMessage(labels.cloud_file_system)}
389
description={intl.formatMessage(MESSAGES.cloud)}
390
/>
391
</Card>
392
)}
393
</Flex>
394
395
{is_commercial && (
396
<>
397
<Divider plain>
398
<Icon name="medkit" /> {intl.formatMessage(labels.support)}
399
</Divider>
400
<Flex {...FLEX_PROPS}>
401
<Card
402
{...CARD_PROPS}
403
onClick={() => handleNavigate("settings/support")}
404
>
405
<Card.Meta
406
avatar={<Icon name="medkit" />}
407
title={intl.formatMessage(labels.support)}
408
description={intl.formatMessage(MESSAGES.support)}
409
/>
410
</Card>
411
</Flex>
412
</>
413
)}
414
</div>
415
);
416
}
417
418