Path: blob/master/src/packages/frontend/account/account-page.tsx
1503 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6The account page. This is what you see when you7click "Account" in the upper right. It has tabs8for different account related information9and configuration.10*/1112// cSpell:ignore payg1314import { Flex, Menu, Space } from "antd";15import { useEffect } from "react";16import { useIntl } from "react-intl";17import { SignOut } from "@cocalc/frontend/account/sign-out";18import {19React,20redux,21useIsMountedRef,22useTypedRedux,23useWindowDimensions,24} from "@cocalc/frontend/app-framework";25import { Icon, Loading } from "@cocalc/frontend/components";26import { cloudFilesystemsEnabled } from "@cocalc/frontend/compute";27import CloudFilesystems from "@cocalc/frontend/compute/cloud-filesystem/cloud-filesystems";28import { Footer } from "@cocalc/frontend/customize";29import { appBasePath } from "@cocalc/frontend/customize/app-base-path";30import { labels } from "@cocalc/frontend/i18n";31import BalanceButton from "@cocalc/frontend/purchases/balance-button";32import PayAsYouGoPage from "@cocalc/frontend/purchases/payg-page";33import PaymentMethodsPage from "@cocalc/frontend/purchases/payment-methods-page";34import PaymentsPage from "@cocalc/frontend/purchases/payments-page";35import PurchasesPage from "@cocalc/frontend/purchases/purchases-page";36import StatementsPage from "@cocalc/frontend/purchases/statements-page";37import SubscriptionsPage from "@cocalc/frontend/purchases/subscriptions-page";38import { SupportTickets } from "@cocalc/frontend/support";39import {40KUCALC_COCALC_COM,41KUCALC_ON_PREMISES,42} from "@cocalc/util/db-schema/site-defaults";43import { AccountPreferences } from "./account-preferences";44import { I18NSelector } from "./i18n-selector";45import { LicensesPage } from "./licenses/licenses-page";46import { PublicPaths } from "./public-paths/public-paths";47import { UpgradesPage } from "./upgrades/upgrades-page";4849// give up on trying to load account info and redirect to landing page.50// Do NOT make too short, since loading account info might takes ~10 seconds, e,g., due51// to slow network or some backend failure that times and retires involvin52// changefeeds.53const LOAD_ACCOUNT_INFO_TIMEOUT = 15_000;5455export const AccountPage: React.FC = () => {56const intl = useIntl();5758const { width: windowWidth } = useWindowDimensions();59const isWide = windowWidth > 800;6061const active_page = useTypedRedux("account", "active_page") ?? "account";62const is_logged_in = useTypedRedux("account", "is_logged_in");63const account_id = useTypedRedux("account", "account_id");64const is_anonymous = useTypedRedux("account", "is_anonymous");65const kucalc = useTypedRedux("customize", "kucalc");66const is_commercial = useTypedRedux("customize", "is_commercial");67const get_api_key = useTypedRedux("page", "get_api_key");6869function handle_select(key: string): void {70switch (key) {71case "billing":72redux.getActions("billing").update_customer();73break;74case "support":75break;76case "signout":77return;78}79redux.getActions("account").set_active_tab(key);80redux.getActions("account").push_state(`/${key}`);81}8283function getTabs(): any[] {84const items: any[] = [85{86key: "account",87label: (88<span>89<Icon name="address-card" />{" "}90{intl.formatMessage(labels.preferences)}91</span>92),93children: (active_page == null || active_page === "account") && (94<AccountPreferences />95),96},97];98// adds a few conditional tabs99if (is_anonymous) {100// None of the rest make any sense for a temporary anonymous account.101return items;102}103items.push({ type: "divider" });104105if (is_commercial) {106items.push({107key: "subscriptions",108label: (109<span>110<Icon name="calendar" /> {intl.formatMessage(labels.subscriptions)}111</span>112),113children: active_page === "subscriptions" && <SubscriptionsPage />,114});115items.push({116key: "licenses",117label: (118<span>119<Icon name="key" /> {intl.formatMessage(labels.licenses)}120</span>121),122children: active_page === "licenses" && <LicensesPage />,123});124items.push({125key: "payg",126label: (127<span>128<Icon name="line-chart" /> Pay As You Go129</span>130),131children: active_page === "payg" && <PayAsYouGoPage />,132});133if (is_commercial && kucalc === KUCALC_COCALC_COM) {134// these have been deprecated for ~ 5 years, but some customers still have them.135items.push({136key: "upgrades",137label: (138<span>139<Icon name="arrow-circle-up" />{" "}140{intl.formatMessage(labels.upgrades)}141</span>142),143children: active_page === "upgrades" && <UpgradesPage />,144});145}146items.push({ type: "divider" });147items.push({148key: "purchases",149label: (150<span>151<Icon name="money-check" /> {intl.formatMessage(labels.purchases)}152</span>153),154children: active_page === "purchases" && <PurchasesPage />,155});156items.push({157key: "payments",158label: (159<span>160<Icon name="credit-card" /> Payments161</span>162),163children: active_page === "payments" && <PaymentsPage />,164});165items.push({166key: "payment-methods",167label: (168<span>169<Icon name="credit-card" /> Payment Methods170</span>171),172children: active_page === "payment-methods" && <PaymentMethodsPage />,173});174items.push({175key: "statements",176label: (177<span>178<Icon name="calendar-week" />{" "}179{intl.formatMessage(labels.statements)}180</span>181),182children: active_page === "statements" && <StatementsPage />,183});184items.push({ type: "divider" });185}186187items.push({188key: "public-files",189label: (190<span>191<Icon name="share-square" />{" "}192{intl.formatMessage(labels.published_files)}193</span>194),195children: active_page === "public-files" && <PublicPaths />,196});197if (cloudFilesystemsEnabled()) {198items.push({199key: "cloud-filesystems",200label: (201<>202<Icon name="server" style={{ marginRight: "5px" }} />203{intl.formatMessage(labels.cloud_file_system)}204</>205),206children: <CloudFilesystems noTitle />,207});208}209210if (211kucalc === KUCALC_COCALC_COM ||212kucalc === KUCALC_ON_PREMISES ||213is_commercial214) {215}216217if (is_commercial) {218items.push({ type: "divider" });219items.push({220key: "support",221label: (222<span>223<Icon name="medkit" /> {intl.formatMessage(labels.support)}224</span>225),226children: active_page === "support" && <SupportTickets />,227});228}229230return items;231}232233function renderExtraContent() {234return (235<Space>236{is_commercial ? <BalanceButton /> : undefined}237<I18NSelector isWide={isWide} />238<SignOut everywhere={false} highlight={true} narrow={!isWide} />239</Space>240);241}242243function render_logged_in_view(): React.JSX.Element {244if (!account_id) {245return (246<div style={{ textAlign: "center", paddingTop: "15px" }}>247<Loading theme={"medium"} />248</div>249);250}251if (is_anonymous) {252return (253<div254style={{255margin: "15px 0 0 0",256padding: "0 10% 0 10%",257overflow: "auto",258}}259>260<AccountPreferences />261</div>262);263}264265const tabs = getTabs();266267// NOTE: This is a bit weird, since I rewrote it form antd Tabs268// to an antd Menu, but didn't change any of the above.269// Antd Menu has a notion of children and submenus, which we *could*270// use but aren't using yet.271const children = {};272const titles = {};273for (const tab of tabs) {274if (tab.type == "divider") {275continue;276}277children[tab.key] = tab.children;278titles[tab.key] = tab.label;279delete tab.children;280}281282return (283<div className="smc-vfill" style={{ flexDirection: "row" }}>284<div285style={{286background: "#00000005",287borderRight: "1px solid rgba(5, 5, 5, 0.06)",288}}289>290<div291style={{292textAlign: "center",293margin: "15px 0",294fontSize: "11pt",295}}296>297<b>Account Configuration</b>298</div>299<Menu300onClick={(e) => {301handle_select(e.key);302}}303selectedKeys={[active_page]}304mode="vertical"305items={tabs}306style={{ width: 183, background: "#00000005", height: "100vh" }}307/>308</div>309<div310className="smc-vfill"311style={{312overflow: "auto",313paddingLeft: "15px",314paddingRight: "15px",315}}316>317<Flex style={{ marginTop: "5px" }}>318<h2>{titles[active_page]}</h2>319<div style={{ flex: 1 }} />320{renderExtraContent()}321</Flex>322{children[active_page]}323<Footer />324</div>325</div>326);327}328329return (330<div className="smc-vfill">331{is_logged_in && !get_api_key ? (332render_logged_in_view()333) : (334<RedirectToNextApp />335)}336</div>337);338};339340declare var DEBUG;341342function RedirectToNextApp({}) {343const isMountedRef = useIsMountedRef();344345useEffect(() => {346const f = () => {347if (isMountedRef.current && !DEBUG) {348// didn't get signed in so go to landing page349window.location.href = appBasePath;350}351};352setTimeout(f, LOAD_ACCOUNT_INFO_TIMEOUT);353}, []);354355return <Loading theme="medium" />;356}357358359