Skip to content

Commit 19ca1d0

Browse files
committed
Merge branch 'novoselt-billing-preferences' into pr-20251208
2 parents b2ed26a + e3d9f92 commit 19ca1d0

23 files changed

+218
-122
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Account page styles
2+
3+
.account-menu-inline-collapsed
4+
.ant-menu-item,
5+
.ant-menu-submenu-title
6+
padding-inline: 0px
7+
text-align: center
8+
9+
.ant-menu-submenu-title
10+
padding-right: 20px
11+
12+
.ant-menu-submenu-arrow
13+
right: 5px

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

Lines changed: 134 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ const LOAD_ACCOUNT_INFO_TIMEOUT = 15_000;
119119
export const AccountPage: React.FC = () => {
120120
const intl = useIntl();
121121
const [hidden, setHidden] = useState(IS_MOBILE);
122-
const [openKeys, setOpenKeys] = useState<string[]>(["preferences"]);
122+
const [openKeys, setOpenKeys] = useState<string[]>([]);
123123

124124
const { width: windowWidth } = useWindowDimensions();
125125
const isWide = windowWidth > 800;
@@ -316,85 +316,98 @@ export const AccountPage: React.FC = () => {
316316
items.push({ type: "divider" });
317317

318318
if (is_commercial) {
319-
items.push({
320-
key: "subscriptions",
321-
label: (
322-
<span>
323-
<Icon name="calendar" /> {intl.formatMessage(labels.subscriptions)}
324-
</span>
325-
),
326-
children: active_page === "subscriptions" && <SubscriptionsPage />,
327-
});
328-
items.push({
329-
key: "licenses",
330-
label: (
331-
<span>
332-
<Icon name="key" /> {intl.formatMessage(labels.licenses)}
333-
</span>
334-
),
335-
children: active_page === "licenses" && <LicensesPage />,
336-
});
337-
items.push({
338-
key: "payg",
339-
label: (
340-
<span>
341-
<Icon name="line-chart" />{" "}
342-
{intl.formatMessage(labels.pay_as_you_go)}
343-
</span>
344-
),
345-
children: active_page === "payg" && <PayAsYouGoPage />,
346-
});
347-
if (is_commercial && kucalc === KUCALC_COCALC_COM) {
348-
// these have been deprecated for ~ 5 years, but some customers still have them.
349-
items.push({
350-
key: "upgrades",
319+
const billingChildren = [
320+
{
321+
key: "subscriptions",
351322
label: (
352323
<span>
353-
<Icon name="arrow-circle-up" />{" "}
354-
{intl.formatMessage(labels.upgrades)}
324+
<Icon name="calendar" />{" "}
325+
{intl.formatMessage(labels.subscriptions)}
355326
</span>
356327
),
357-
children: active_page === "upgrades" && <UpgradesPage />,
358-
});
359-
}
360-
items.push({ type: "divider" });
361-
items.push({
362-
key: "purchases",
363-
label: (
364-
<span>
365-
<Icon name="money-check" /> {intl.formatMessage(labels.purchases)}
366-
</span>
367-
),
368-
children: active_page === "purchases" && <PurchasesPage />,
369-
});
370-
items.push({
371-
key: "payments",
372-
label: (
373-
<span>
374-
<Icon name="credit-card" /> {intl.formatMessage(labels.payments)}
375-
</span>
376-
),
377-
children: active_page === "payments" && <PaymentsPage />,
378-
});
379-
items.push({
380-
key: "payment-methods",
381-
label: (
382-
<span>
383-
<Icon name="credit-card" />{" "}
384-
{intl.formatMessage(labels.payment_methods)}
385-
</span>
386-
),
387-
children: active_page === "payment-methods" && <PaymentMethodsPage />,
388-
});
328+
children: active_page === "subscriptions" && <SubscriptionsPage />,
329+
},
330+
{
331+
key: "licenses",
332+
label: (
333+
<span>
334+
<Icon name="key" /> {intl.formatMessage(labels.licenses)}
335+
</span>
336+
),
337+
children: active_page === "licenses" && <LicensesPage />,
338+
},
339+
{
340+
key: "payg",
341+
label: (
342+
<span>
343+
<Icon name="line-chart" />{" "}
344+
{intl.formatMessage(labels.pay_as_you_go)}
345+
</span>
346+
),
347+
children: active_page === "payg" && <PayAsYouGoPage />,
348+
},
349+
...(kucalc === KUCALC_COCALC_COM
350+
? [
351+
{
352+
key: "upgrades",
353+
label: (
354+
<span>
355+
<Icon name="arrow-circle-up" />{" "}
356+
{intl.formatMessage(labels.upgrades)}
357+
</span>
358+
),
359+
children: active_page === "upgrades" && <UpgradesPage />,
360+
},
361+
]
362+
: []),
363+
{
364+
key: "purchases",
365+
label: (
366+
<span>
367+
<Icon name="money-check" /> {intl.formatMessage(labels.purchases)}
368+
</span>
369+
),
370+
children: active_page === "purchases" && <PurchasesPage />,
371+
},
372+
{
373+
key: "payments",
374+
label: (
375+
<span>
376+
<Icon name="credit-card" /> {intl.formatMessage(labels.payments)}
377+
</span>
378+
),
379+
children: active_page === "payments" && <PaymentsPage />,
380+
},
381+
{
382+
key: "payment-methods",
383+
label: (
384+
<span>
385+
<Icon name="credit-card" />{" "}
386+
{intl.formatMessage(labels.payment_methods)}
387+
</span>
388+
),
389+
children: active_page === "payment-methods" && <PaymentMethodsPage />,
390+
},
391+
{
392+
key: "statements",
393+
label: (
394+
<span>
395+
<Icon name="calendar-week" />{" "}
396+
{intl.formatMessage(labels.statements)}
397+
</span>
398+
),
399+
children: active_page === "statements" && <StatementsPage />,
400+
},
401+
];
402+
389403
items.push({
390-
key: "statements",
404+
key: "billing",
391405
label: (
392406
<span>
393-
<Icon name="calendar-week" />{" "}
394-
{intl.formatMessage(labels.statements)}
407+
<Icon name="money-check" /> {intl.formatMessage(labels.billing)}
395408
</span>
396409
),
397-
children: active_page === "statements" && <StatementsPage />,
410+
children: billingChildren,
398411
});
399412
items.push({ type: "divider" });
400413
}
@@ -446,6 +459,40 @@ export const AccountPage: React.FC = () => {
446459
}
447460

448461
const tabs = getTabs();
462+
const parentByChildKey = new Map<string, string>();
463+
464+
useEffect(() => {
465+
const parentKey =
466+
parentByChildKey.get(active_sub_tab ?? "") ??
467+
parentByChildKey.get(active_page ?? "");
468+
setOpenKeys((prevOpenKeys) =>
469+
parentKey == null
470+
? []
471+
: prevOpenKeys.length === 1 && prevOpenKeys[0] === parentKey
472+
? prevOpenKeys
473+
: [parentKey],
474+
);
475+
}, [active_page, active_sub_tab]);
476+
477+
useEffect(() => {
478+
if (
479+
active_sub_tab &&
480+
parentByChildKey.get(active_sub_tab) !== active_page
481+
) {
482+
redux.getActions("account").setState({ active_sub_tab: undefined });
483+
}
484+
}, [active_page, active_sub_tab, parentByChildKey]);
485+
486+
function handleOpenChange(keys: string[]) {
487+
setOpenKeys((prevOpenKeys) => {
488+
const newlyOpened = keys.find((key) => !prevOpenKeys.includes(key));
489+
return newlyOpened ? [newlyOpened] : [];
490+
});
491+
}
492+
493+
function visibleLabel(label) {
494+
return hidden ? <span>{label.props.children[0]}</span> : label;
495+
}
449496

450497
// Process tabs to handle nested children for sub-tabs
451498
const children = {};
@@ -454,68 +501,35 @@ export const AccountPage: React.FC = () => {
454501
if (tab.type == "divider") {
455502
continue;
456503
}
457-
if (tab.key === "preferences" && Array.isArray(tab.children)) {
458-
// Handle sub-tabs for preferences
504+
const originalLabel = tab.label;
505+
titles[tab.key] = originalLabel;
506+
tab.label = visibleLabel(originalLabel);
507+
508+
if (Array.isArray(tab.children)) {
509+
// Handle nested submenus generically (preferences, billing, etc.)
459510
const subTabs = tab.children;
460511
tab.children = subTabs.map((subTab) => {
461-
// Extract just the icon (first child) from the span when hidden
462-
const label = hidden ? (
463-
<span style={{ paddingLeft: "5px" }}>
464-
{subTab.label.props.children[0]}
465-
</span>
466-
) : (
467-
subTab.label
468-
);
512+
const label = visibleLabel(subTab.label);
469513
return {
470514
key: subTab.key,
471515
label,
472516
};
473517
});
474-
// Store sub-tab children and full labels
475518
for (const subTab of subTabs) {
519+
// Track child -> parent mapping for openKeys/title lookup.
520+
parentByChildKey.set(subTab.key, tab.key);
476521
children[subTab.key] = subTab.children;
477522
titles[subTab.key] = subTab.label; // Always store original full label
478523
}
479-
} else if (tab.key === "settings" || tab.key === "profile") {
480-
// Handle settings and profile as top-level pages
481-
// Store original full label for renderTitle()
482-
const originalLabel = tab.label;
483-
// Extract just the icon (first child) from the span when hidden
484-
tab.label = hidden ? (
485-
<span style={{ paddingLeft: "5px" }}>
486-
{tab.label.props.children[0]}
487-
</span>
488-
) : (
489-
tab.label
490-
);
491-
children[tab.key] = tab.children;
492-
titles[tab.key] = originalLabel; // Store original label
493-
delete tab.children;
494524
} else {
495-
// Store original full label for renderTitle()
496-
const originalLabel = tab.label;
497-
// Extract just the icon (first child) from the span when hidden
498-
tab.label = hidden ? (
499-
<span style={{ paddingLeft: "5px" }}>
500-
{tab.label.props.children[0]}
501-
</span>
502-
) : (
503-
tab.label
504-
);
505525
children[tab.key] = tab.children;
506-
titles[tab.key] = originalLabel; // Store original label
507526
delete tab.children;
508527
}
509528
}
510529

530+
const activeChildKey = active_sub_tab ?? active_page;
511531
function renderTitle() {
512-
return (
513-
<Title level={3}>
514-
{active_page === "preferences" && active_sub_tab
515-
? titles[active_sub_tab]
516-
: titles[active_page]}
517-
</Title>
518-
);
532+
return <Title level={3}>{titles[activeChildKey]}</Title>;
519533
}
520534

521535
function renderExtraContent() {
@@ -565,9 +579,9 @@ export const AccountPage: React.FC = () => {
565579
}}
566580
>
567581
<Menu
568-
defaultOpenKeys={["preferences"]}
569-
openKeys={hidden ? ["preferences"] : openKeys}
570-
onOpenChange={hidden ? undefined : setOpenKeys}
582+
className={hidden ? "account-menu-inline-collapsed" : undefined}
583+
openKeys={openKeys}
584+
onOpenChange={handleOpenChange}
571585
mode="inline"
572586
items={tabs}
573587
onClick={(e) => {
@@ -578,7 +592,7 @@ export const AccountPage: React.FC = () => {
578592
}
579593
inlineIndent={hidden ? 0 : 24}
580594
style={{
581-
width: hidden ? 50 : 200,
595+
width: hidden ? 50 : 220,
582596
background: "#00000005",
583597
flex: "1 1 auto",
584598
overflowY: "auto",
@@ -622,9 +636,7 @@ export const AccountPage: React.FC = () => {
622636
<div style={{ flex: 1 }} />
623637
{renderExtraContent()}
624638
</Flex>
625-
{active_page === "preferences" && active_sub_tab
626-
? children[active_sub_tab]
627-
: children[active_page]}
639+
{children[activeChildKey] ?? children[active_page]}
628640
<Footer />
629641
</div>
630642
</div>

0 commit comments

Comments
 (0)