Skip to content

Commit e0929f1

Browse files
committed
frontend/settings: better type safety for navigation
1 parent f309e1d commit e0929f1

File tree

6 files changed

+171
-85
lines changed

6 files changed

+171
-85
lines changed

src/packages/frontend/account/account-page.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ and configuration.
1212

1313
// cSpell:ignore payg
1414

15-
import type { PreferencesSubTabKey, PreferencesSubTabType } from "./types";
15+
import type {
16+
PreferencesSubTabKey,
17+
PreferencesSubTabType,
18+
} from "@cocalc/util/types/settings";
1619

1720
import { Flex, Menu, Space } from "antd";
1821
import { useEffect } from "react";
@@ -79,17 +82,28 @@ import { I18NSelector } from "./i18n-selector";
7982
import { LicensesPage } from "./licenses/licenses-page";
8083
import { PublicPaths } from "./public-paths/public-paths";
8184
import { SettingsOverview } from "./settings-index";
82-
import { VALID_PREFERENCES_SUB_TYPES } from "./types";
85+
import { VALID_PREFERENCES_SUB_TYPES } from "@cocalc/util/types/settings";
8386
import { UpgradesPage } from "./upgrades/upgrades-page";
8487

8588
export const ACCOUNT_SETTINGS_ICON_NAME: IconName = "settings";
8689

90+
// Type for valid menu keys
91+
type MenuKey =
92+
| "settings"
93+
| "billing"
94+
| "support"
95+
| "signout"
96+
| "profile"
97+
| PreferencesSubTabKey
98+
| string;
99+
87100
// Utility function to safely create preferences sub-tab key
88101
function createPreferencesSubTabKey(
89102
subTab: string,
90103
): PreferencesSubTabKey | null {
91104
if (VALID_PREFERENCES_SUB_TYPES.includes(subTab as PreferencesSubTabType)) {
92-
return `preferences-${subTab}` as PreferencesSubTabKey;
105+
const validSubTab = subTab as PreferencesSubTabType;
106+
return `preferences-${validSubTab}`;
93107
}
94108
return null;
95109
}
@@ -119,7 +133,7 @@ export const AccountPage: React.FC = () => {
119133
const is_commercial = useTypedRedux("customize", "is_commercial");
120134
const get_api_key = useTypedRedux("page", "get_api_key");
121135

122-
function handle_select(key: string): void {
136+
function handle_select(key: MenuKey): void {
123137
switch (key) {
124138
case "settings":
125139
// Handle settings overview page
@@ -147,7 +161,7 @@ export const AccountPage: React.FC = () => {
147161
}
148162

149163
// Handle sub-tabs under preferences
150-
if (key.startsWith("preferences-")) {
164+
if (typeof key === "string" && key.startsWith("preferences-")) {
151165
const subTab = key.replace("preferences-", "");
152166
const subTabKey = createPreferencesSubTabKey(subTab);
153167
if (subTabKey) {

src/packages/frontend/account/settings-index.tsx

Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@ import { Icon } from "@cocalc/frontend/components";
1313
import AIAvatar from "@cocalc/frontend/components/ai-avatar";
1414
import { cloudFilesystemsEnabled } from "@cocalc/frontend/compute";
1515
import { labels } from "@cocalc/frontend/i18n";
16-
import { COLORS } from "@cocalc/util/theme";
1716
import { APPEARANCE_ICON_NAME } from "./account-preferences-appearance";
1817
import { COMMUNICATION_ICON_NAME } from "./account-preferences-communication";
1918
import { EDITOR_ICON_NAME } from "./account-preferences-editor";
2019
import { KEYBOARD_ICON_NAME } from "./account-preferences-keyboard";
2120
import { OTHER_ICON_NAME } from "./account-preferences-other";
2221
import { ACCOUNT_PROFILE_ICON_NAME } from "./account-preferences-profile";
2322
import { KEYS_ICON_NAME } from "./account-preferences-security";
23+
import {
24+
VALID_PREFERENCES_SUB_TYPES,
25+
type NavigatePath,
26+
type PreferencesSubTabType,
27+
} from "@cocalc/util/types/settings";
28+
// import { COLORS } from "@cocalc/util/theme";
2429

25-
const messages = defineMessages({
30+
const MESSAGES = defineMessages({
2631
title: {
2732
id: "account.settings.overview.title",
2833
defaultMessage: "Settings Overview",
@@ -32,11 +37,6 @@ const messages = defineMessages({
3237
defaultMessage:
3338
"Manage your personal information, avatar, and account details.",
3439
},
35-
preferences: {
36-
id: "account.settings.overview.preferences",
37-
defaultMessage:
38-
"Customize your experience by configuring the appearance, editor settings, and other preferences.",
39-
},
4040
appearance: {
4141
id: "account.settings.overview.appearance",
4242
defaultMessage:
@@ -45,28 +45,27 @@ const messages = defineMessages({
4545
editor: {
4646
id: "account.settings.overview.editor",
4747
defaultMessage:
48-
"Customize code editor behavior, fonts, indentation, and display options.",
48+
"Customize code editor behavior, indentation, and content options.",
4949
},
5050
keyboard: {
5151
id: "account.settings.overview.keyboard",
52-
defaultMessage: "Keyboard shortcuts and input method preferences.",
52+
defaultMessage: "Keyboard shortcuts.",
5353
},
5454
ai: {
5555
id: "account.settings.overview.ai",
5656
defaultMessage: "Configure AI assistant settings and integrations.",
5757
},
5858
communication: {
5959
id: "account.settings.overview.communication",
60-
defaultMessage:
61-
"Manage notification preferences and communication settings.",
60+
defaultMessage: "Notification preferences and communication settings.",
6261
},
6362
keys: {
6463
id: "account.settings.overview.keys",
6564
defaultMessage: "Manage API keys and setup SSH keys.",
6665
},
6766
other: {
6867
id: "account.settings.overview.other",
69-
defaultMessage: "Additional miscellaneous settings and options.",
68+
defaultMessage: "Miscellaneous settings and options.",
7069
},
7170
billing: {
7271
id: "account.settings.overview.billing",
@@ -102,7 +101,7 @@ const messages = defineMessages({
102101
},
103102
cloud: {
104103
id: "account.settings.overview.cloud",
105-
defaultMessage: "Configure cloud file system connections.",
104+
defaultMessage: "Manage cloud file system storage.",
106105
},
107106
support: {
108107
id: "account.settings.overview.support",
@@ -116,13 +115,14 @@ const CARD_PROPS = {
116115
style: { width: 300, minWidth: 250 },
117116
} as const;
118117

119-
const HIGHLIGHTED_CARD_PROPS = {
120-
...CARD_PROPS,
121-
style: {
122-
...CARD_PROPS.style,
123-
border: `1px solid ${COLORS.BLUE_LLL}`,
124-
},
125-
} as const;
118+
// TODO: highlighting a few cards is a good idea, but better to do this later...
119+
const HIGHLIGHTED_CARD_PROPS = CARD_PROPS; // = {
120+
// ...CARD_PROPS,
121+
// style: {
122+
// ...CARD_PROPS.style,
123+
// border: `2px solid ${COLORS.BLUE_LLL}`,
124+
// },
125+
// } as const;
126126

127127
const FLEX_PROPS = {
128128
wrap: true as const,
@@ -134,7 +134,7 @@ export function SettingsOverview() {
134134
const intl = useIntl();
135135
const is_commercial = useTypedRedux("customize", "is_commercial");
136136

137-
const handleNavigate = (path: string) => {
137+
function handleNavigate(path: NavigatePath) {
138138
// Use the same navigation pattern as the account page
139139
const segments = path.split("/").filter(Boolean);
140140
if (segments[0] === "settings") {
@@ -146,20 +146,22 @@ export function SettingsOverview() {
146146
redux.getActions("account").push_state(`/profile`);
147147
} else if (segments[1] === "preferences" && segments[2]) {
148148
// Handle preferences sub-tabs
149-
const subTab = segments[2];
150-
const subTabKey = `preferences-${subTab}` as any;
151-
redux.getActions("account").setState({
152-
active_page: "preferences",
153-
active_sub_tab: subTabKey,
154-
});
155-
redux.getActions("account").push_state(`/preferences/${subTab}`);
149+
const subTab = segments[2] as PreferencesSubTabType;
150+
if (VALID_PREFERENCES_SUB_TYPES.includes(subTab)) {
151+
const subTabKey = `preferences-${subTab}` as const;
152+
redux.getActions("account").setState({
153+
active_page: "preferences",
154+
active_sub_tab: subTabKey,
155+
});
156+
redux.getActions("account").push_state(`/preferences/${subTab}`);
157+
}
156158
} else {
157159
// Handle other settings pages
158160
redux.getActions("account").set_active_tab(segments[1]);
159161
redux.getActions("account").push_state(`/${segments[1]}`);
160162
}
161163
}
162-
};
164+
}
163165

164166
return (
165167
<div style={{ padding: "20px" }}>
@@ -171,7 +173,7 @@ export function SettingsOverview() {
171173
<Card.Meta
172174
avatar={<Icon name={ACCOUNT_PROFILE_ICON_NAME} />}
173175
title={intl.formatMessage(labels.profile)}
174-
description={intl.formatMessage(messages.profile)}
176+
description={intl.formatMessage(MESSAGES.profile)}
175177
/>
176178
</Card>
177179
<Card
@@ -181,7 +183,7 @@ export function SettingsOverview() {
181183
<Card.Meta
182184
avatar={<Icon name={APPEARANCE_ICON_NAME} />}
183185
title={intl.formatMessage(labels.appearance)}
184-
description={intl.formatMessage(messages.appearance)}
186+
description={intl.formatMessage(MESSAGES.appearance)}
185187
/>
186188
</Card>
187189
<Card
@@ -191,7 +193,7 @@ export function SettingsOverview() {
191193
<Card.Meta
192194
avatar={<Icon name={EDITOR_ICON_NAME} />}
193195
title={intl.formatMessage(labels.editor)}
194-
description={intl.formatMessage(messages.editor)}
196+
description={intl.formatMessage(MESSAGES.editor)}
195197
/>
196198
</Card>
197199
<Card
@@ -201,7 +203,7 @@ export function SettingsOverview() {
201203
<Card.Meta
202204
avatar={<Icon name={KEYBOARD_ICON_NAME} />}
203205
title={intl.formatMessage(labels.keyboard)}
204-
description={intl.formatMessage(messages.keyboard)}
206+
description={intl.formatMessage(MESSAGES.keyboard)}
205207
/>
206208
</Card>
207209
<Card
@@ -211,7 +213,7 @@ export function SettingsOverview() {
211213
<Card.Meta
212214
avatar={<AIAvatar size={24} />}
213215
title={intl.formatMessage(labels.ai)}
214-
description={intl.formatMessage(messages.ai)}
216+
description={intl.formatMessage(MESSAGES.ai)}
215217
/>
216218
</Card>
217219
<Card
@@ -221,7 +223,7 @@ export function SettingsOverview() {
221223
<Card.Meta
222224
avatar={<Icon name={COMMUNICATION_ICON_NAME} />}
223225
title={intl.formatMessage(labels.communication)}
224-
description={intl.formatMessage(messages.communication)}
226+
description={intl.formatMessage(MESSAGES.communication)}
225227
/>
226228
</Card>
227229
<Card
@@ -231,7 +233,7 @@ export function SettingsOverview() {
231233
<Card.Meta
232234
avatar={<Icon name={KEYS_ICON_NAME} />}
233235
title={intl.formatMessage(labels.ssh_and_api_keys)}
234-
description={intl.formatMessage(messages.keys)}
236+
description={intl.formatMessage(MESSAGES.keys)}
235237
/>
236238
</Card>
237239
<Card
@@ -241,7 +243,7 @@ export function SettingsOverview() {
241243
<Card.Meta
242244
avatar={<Icon name={OTHER_ICON_NAME} />}
243245
title={intl.formatMessage(labels.other)}
244-
description={intl.formatMessage(messages.other)}
246+
description={intl.formatMessage(MESSAGES.other)}
245247
/>
246248
</Card>
247249
</Flex>
@@ -259,7 +261,7 @@ export function SettingsOverview() {
259261
<Card.Meta
260262
avatar={<Icon name="calendar" />}
261263
title={intl.formatMessage(labels.subscriptions)}
262-
description={intl.formatMessage(messages.subscriptions)}
264+
description={intl.formatMessage(MESSAGES.subscriptions)}
263265
/>
264266
</Card>
265267
<Card
@@ -269,7 +271,7 @@ export function SettingsOverview() {
269271
<Card.Meta
270272
avatar={<Icon name="key" />}
271273
title={intl.formatMessage(labels.licenses)}
272-
description={intl.formatMessage(messages.licenses)}
274+
description={intl.formatMessage(MESSAGES.licenses)}
273275
/>
274276
</Card>
275277
<Card
@@ -279,7 +281,7 @@ export function SettingsOverview() {
279281
<Card.Meta
280282
avatar={<Icon name="line-chart" />}
281283
title={intl.formatMessage(labels.pay_as_you_go)}
282-
description={intl.formatMessage(messages.payg)}
284+
description={intl.formatMessage(MESSAGES.payg)}
283285
/>
284286
</Card>
285287
<Card
@@ -289,7 +291,7 @@ export function SettingsOverview() {
289291
<Card.Meta
290292
avatar={<Icon name="money-check" />}
291293
title={intl.formatMessage(labels.purchases)}
292-
description={intl.formatMessage(messages.purchases)}
294+
description={intl.formatMessage(MESSAGES.purchases)}
293295
/>
294296
</Card>
295297
<Card
@@ -299,7 +301,7 @@ export function SettingsOverview() {
299301
<Card.Meta
300302
avatar={<Icon name="credit-card" />}
301303
title={intl.formatMessage(labels.payments)}
302-
description={intl.formatMessage(messages.payments)}
304+
description={intl.formatMessage(MESSAGES.payments)}
303305
/>
304306
</Card>
305307
<Card
@@ -309,7 +311,7 @@ export function SettingsOverview() {
309311
<Card.Meta
310312
avatar={<Icon name="calendar-week" />}
311313
title={intl.formatMessage(labels.statements)}
312-
description={intl.formatMessage(messages.statements)}
314+
description={intl.formatMessage(MESSAGES.statements)}
313315
/>
314316
</Card>
315317
</Flex>
@@ -327,7 +329,7 @@ export function SettingsOverview() {
327329
<Card.Meta
328330
avatar={<Icon name="share-square" />}
329331
title={intl.formatMessage(labels.published_files)}
330-
description={intl.formatMessage(messages.files)}
332+
description={intl.formatMessage(MESSAGES.files)}
331333
/>
332334
</Card>
333335

@@ -339,7 +341,7 @@ export function SettingsOverview() {
339341
<Card.Meta
340342
avatar={<Icon name="server" />}
341343
title={intl.formatMessage(labels.cloud_file_system)}
342-
description={intl.formatMessage(messages.cloud)}
344+
description={intl.formatMessage(MESSAGES.cloud)}
343345
/>
344346
</Card>
345347
)}
@@ -358,7 +360,7 @@ export function SettingsOverview() {
358360
<Card.Meta
359361
avatar={<Icon name="medkit" />}
360362
title={intl.formatMessage(labels.support)}
361-
description={intl.formatMessage(messages.support)}
363+
description={intl.formatMessage(MESSAGES.support)}
362364
/>
363365
</Card>
364366
</Flex>

src/packages/frontend/account/types.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { List, Map } from "immutable";
99

1010
import { TypedMap } from "@cocalc/frontend/app-framework";
1111
import type { Locale, OTHER_SETTINGS_LOCALE_KEY } from "@cocalc/frontend/i18n";
12+
import { type AutoBalance } from "@cocalc/util/db-schema/accounts";
1213
import {
1314
NEW_FILENAMES,
1415
NewFilenameTypes,
@@ -17,23 +18,9 @@ import {
1718
import { LanguageModel } from "@cocalc/util/db-schema/llm-utils";
1819
import { OTHER_SETTINGS_REPLY_ENGLISH_KEY } from "@cocalc/util/i18n/const";
1920
import { PassportStrategyFrontend } from "@cocalc/util/types/passport-types";
20-
import { SETTINGS_LANGUAGE_MODEL_KEY } from "./useLanguageModelSetting";
21-
import { type AutoBalance } from "@cocalc/util/db-schema/accounts";
21+
import { type PreferencesSubTabKey } from "@cocalc/util/types/settings";
2222
import { ACTIVITY_BAR_LABELS } from "../project/page/activity-bar-consts";
23-
24-
// Preferences sub-tab types
25-
export const VALID_PREFERENCES_SUB_TYPES = [
26-
"appearance",
27-
"editor",
28-
"keyboard",
29-
"ai",
30-
"communication",
31-
"keys",
32-
"other",
33-
] as const;
34-
export type PreferencesSubTabType =
35-
(typeof VALID_PREFERENCES_SUB_TYPES)[number];
36-
export type PreferencesSubTabKey = `preferences-${PreferencesSubTabType}`;
23+
import { SETTINGS_LANGUAGE_MODEL_KEY } from "./useLanguageModelSetting";
3724

3825
// this is incomplete...
3926

0 commit comments

Comments
 (0)