Path: blob/master/src/packages/frontend/account/settings-index.tsx
2209 views
/*1* This file is part of CoCalc: Copyright © 2025 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45// cSpell:ignore payg67import { Card, Divider, Flex } from "antd";8import { defineMessages, useIntl } from "react-intl";910import { redux, useTypedRedux } from "@cocalc/frontend/app-framework";11import { Icon } from "@cocalc/frontend/components";12import AIAvatar from "@cocalc/frontend/components/ai-avatar";13import { cloudFilesystemsEnabled } from "@cocalc/frontend/compute";14import { labels } from "@cocalc/frontend/i18n";15import { COLORS } from "@cocalc/util/theme";16import {17VALID_PREFERENCES_SUB_TYPES,18type NavigatePath,19type PreferencesSubTabType,20} from "@cocalc/util/types/settings";21import { APPEARANCE_ICON_NAME } from "./account-preferences-appearance";22import { COMMUNICATION_ICON_NAME } from "./account-preferences-communication";23import { EDITOR_ICON_NAME } from "./account-preferences-editor";24import { KEYBOARD_ICON_NAME } from "./account-preferences-keyboard";25import { OTHER_ICON_NAME } from "./account-preferences-other";26import { ACCOUNT_PROFILE_ICON_NAME } from "./account-preferences-profile";27import { KEYS_ICON_NAME } from "./account-preferences-security";2829const MESSAGES = defineMessages({30title: {31id: "account.settings.overview.title",32defaultMessage: "Settings Overview",33},34profile: {35id: "account.settings.overview.profile",36defaultMessage:37"Manage your personal information, avatar, and account details.",38},39appearance: {40id: "account.settings.overview.appearance",41defaultMessage:42"Customize the visual experience via color themes, {dark_mode}, language, and visual settings.",43},44editor: {45id: "account.settings.overview.editor",46defaultMessage:47"Customize code editor behavior, indentation, and content options.",48},49keyboard: {50id: "account.settings.overview.keyboard",51defaultMessage: "Keyboard shortcuts.",52},53ai: {54id: "account.settings.overview.ai",55defaultMessage: "Configure AI assistant settings and integrations.",56},57communication: {58id: "account.settings.overview.communication",59defaultMessage: "Notification preferences and communication settings.",60},61keys: {62id: "account.settings.overview.keys",63defaultMessage: "Manage API keys and setup SSH keys.",64},65other: {66id: "account.settings.overview.other",67defaultMessage: "Miscellaneous settings and options.",68},69billing: {70id: "account.settings.overview.billing",71defaultMessage: "Manage subscriptions, licenses, and billing information.",72},73subscriptions: {74id: "account.settings.overview.subscriptions",75defaultMessage: "View and manage your active subscriptions.",76},77licenses: {78id: "account.settings.overview.licenses",79defaultMessage: "Manage software licenses and access permissions.",80},81payg: {82id: "account.settings.overview.payg",83defaultMessage: "Configure pay-as-you-go usage and billing.",84},85purchases: {86id: "account.settings.overview.purchases",87defaultMessage: "View purchase history and receipts.",88},89payments: {90id: "account.settings.overview.payments",91defaultMessage: "Manage payment methods and transaction history.",92},93statements: {94id: "account.settings.overview.statements",95defaultMessage: "View detailed billing statements and invoices.",96},97files: {98id: "account.settings.overview.files",99defaultMessage: "Manage published files and public sharing.",100},101cloud: {102id: "account.settings.overview.cloud",103defaultMessage: "Manage cloud file system storage.",104},105support: {106id: "account.settings.overview.support",107defaultMessage: "Access support tickets and help resources.",108},109});110111const CARD_PROPS = {112size: "small" as const,113hoverable: true,114style: { width: 300, minWidth: 250 },115} as const;116117// TODO: highlighting a few cards is a good idea, but better to do this later...118const HIGHLIGHTED_CARD_PROPS = CARD_PROPS; // = {119// ...CARD_PROPS,120// style: {121// ...CARD_PROPS.style,122// border: `2px solid ${COLORS.BLUE_LLL}`,123// },124// } as const;125126const FLEX_PROPS = {127wrap: true as const,128gap: "15px",129style: { marginBottom: "40px" },130} as const;131132export function SettingsOverview() {133const intl = useIntl();134const is_commercial = useTypedRedux("customize", "is_commercial");135136function handleNavigate(path: NavigatePath) {137// Use the same navigation pattern as the account page138const segments = path.split("/").filter(Boolean);139if (segments[0] === "settings") {140if (segments[1] === "profile") {141redux.getActions("account").setState({142active_page: "profile",143active_sub_tab: undefined,144});145redux.getActions("account").push_state(`/profile`);146} else if (segments[1] === "preferences" && segments[2]) {147// Handle preferences sub-tabs148const subTab = segments[2] as PreferencesSubTabType;149if (VALID_PREFERENCES_SUB_TYPES.includes(subTab)) {150const subTabKey = `preferences-${subTab}` as const;151redux.getActions("account").setState({152active_page: "preferences",153active_sub_tab: subTabKey,154});155redux.getActions("account").push_state(`/preferences/${subTab}`);156}157} else {158// Handle other settings pages159redux.getActions("account").set_active_tab(segments[1]);160redux.getActions("account").push_state(`/${segments[1]}`);161}162}163}164165return (166<div style={{ padding: "20px" }}>167<Flex {...FLEX_PROPS}>168<Card169{...HIGHLIGHTED_CARD_PROPS}170onClick={() => handleNavigate("settings/profile")}171>172<Card.Meta173avatar={<Icon name={ACCOUNT_PROFILE_ICON_NAME} />}174title={intl.formatMessage(labels.profile)}175description={intl.formatMessage(MESSAGES.profile)}176/>177</Card>178<Card179{...HIGHLIGHTED_CARD_PROPS}180onClick={() => handleNavigate("settings/preferences/appearance")}181>182<Card.Meta183avatar={<Icon name={APPEARANCE_ICON_NAME} />}184title={intl.formatMessage(labels.appearance)}185description={intl.formatMessage(MESSAGES.appearance, {186dark_mode: (187<span188style={{189backgroundColor: COLORS.GRAY_DD,190color: COLORS.GRAY_LLL,191paddingLeft: "3px",192paddingRight: "3px",193}}194>195dark mode196</span>197),198})}199/>200</Card>201<Card202{...CARD_PROPS}203onClick={() => handleNavigate("settings/preferences/editor")}204>205<Card.Meta206avatar={<Icon name={EDITOR_ICON_NAME} />}207title={intl.formatMessage(labels.editor)}208description={intl.formatMessage(MESSAGES.editor)}209/>210</Card>211<Card212{...CARD_PROPS}213onClick={() => handleNavigate("settings/preferences/keyboard")}214>215<Card.Meta216avatar={<Icon name={KEYBOARD_ICON_NAME} />}217title={intl.formatMessage(labels.keyboard)}218description={intl.formatMessage(MESSAGES.keyboard)}219/>220</Card>221<Card222{...CARD_PROPS}223onClick={() => handleNavigate("settings/preferences/ai")}224>225<Card.Meta226avatar={<AIAvatar size={24} />}227title={intl.formatMessage(labels.ai)}228description={intl.formatMessage(MESSAGES.ai)}229/>230</Card>231<Card232{...CARD_PROPS}233onClick={() => handleNavigate("settings/preferences/communication")}234>235<Card.Meta236avatar={<Icon name={COMMUNICATION_ICON_NAME} />}237title={intl.formatMessage(labels.communication)}238description={intl.formatMessage(MESSAGES.communication)}239/>240</Card>241<Card242{...CARD_PROPS}243onClick={() => handleNavigate("settings/preferences/keys")}244>245<Card.Meta246avatar={<Icon name={KEYS_ICON_NAME} />}247title={intl.formatMessage(labels.ssh_and_api_keys)}248description={intl.formatMessage(MESSAGES.keys)}249/>250</Card>251<Card252{...CARD_PROPS}253onClick={() => handleNavigate("settings/preferences/other")}254>255<Card.Meta256avatar={<Icon name={OTHER_ICON_NAME} />}257title={intl.formatMessage(labels.other)}258description={intl.formatMessage(MESSAGES.other)}259/>260</Card>261</Flex>262263{is_commercial && (264<>265<Divider plain>266<Icon name="money-check" /> {intl.formatMessage(labels.billing)}267</Divider>268<Flex {...FLEX_PROPS}>269<Card270{...HIGHLIGHTED_CARD_PROPS}271onClick={() => handleNavigate("settings/subscriptions")}272>273<Card.Meta274avatar={<Icon name="calendar" />}275title={intl.formatMessage(labels.subscriptions)}276description={intl.formatMessage(MESSAGES.subscriptions)}277/>278</Card>279<Card280{...HIGHLIGHTED_CARD_PROPS}281onClick={() => handleNavigate("settings/licenses")}282>283<Card.Meta284avatar={<Icon name="key" />}285title={intl.formatMessage(labels.licenses)}286description={intl.formatMessage(MESSAGES.licenses)}287/>288</Card>289<Card290{...CARD_PROPS}291onClick={() => handleNavigate("settings/payg")}292>293<Card.Meta294avatar={<Icon name="line-chart" />}295title={intl.formatMessage(labels.pay_as_you_go)}296description={intl.formatMessage(MESSAGES.payg)}297/>298</Card>299<Card300{...CARD_PROPS}301onClick={() => handleNavigate("settings/purchases")}302>303<Card.Meta304avatar={<Icon name="money-check" />}305title={intl.formatMessage(labels.purchases)}306description={intl.formatMessage(MESSAGES.purchases)}307/>308</Card>309<Card310{...CARD_PROPS}311onClick={() => handleNavigate("settings/payments")}312>313<Card.Meta314avatar={<Icon name="credit-card" />}315title={intl.formatMessage(labels.payments)}316description={intl.formatMessage(MESSAGES.payments)}317/>318</Card>319<Card320{...CARD_PROPS}321onClick={() => handleNavigate("settings/statements")}322>323<Card.Meta324avatar={<Icon name="calendar-week" />}325title={intl.formatMessage(labels.statements)}326description={intl.formatMessage(MESSAGES.statements)}327/>328</Card>329</Flex>330</>331)}332333<Divider plain>334<Icon name="files" /> {intl.formatMessage(labels.files)}335</Divider>336<Flex {...FLEX_PROPS}>337<Card338{...CARD_PROPS}339onClick={() => handleNavigate("settings/public-files")}340>341<Card.Meta342avatar={<Icon name="share-square" />}343title={intl.formatMessage(labels.published_files)}344description={intl.formatMessage(MESSAGES.files)}345/>346</Card>347348{cloudFilesystemsEnabled() && (349<Card350{...CARD_PROPS}351onClick={() => handleNavigate("settings/cloud-filesystems")}352>353<Card.Meta354avatar={<Icon name="server" />}355title={intl.formatMessage(labels.cloud_file_system)}356description={intl.formatMessage(MESSAGES.cloud)}357/>358</Card>359)}360</Flex>361362{is_commercial && (363<>364<Divider plain>365<Icon name="medkit" /> {intl.formatMessage(labels.support)}366</Divider>367<Flex {...FLEX_PROPS}>368<Card369{...CARD_PROPS}370onClick={() => handleNavigate("settings/support")}371>372<Card.Meta373avatar={<Icon name="medkit" />}374title={intl.formatMessage(labels.support)}375description={intl.formatMessage(MESSAGES.support)}376/>377</Card>378</Flex>379</>380)}381</div>382);383}384385386