@@ -119,7 +119,7 @@ const LOAD_ACCOUNT_INFO_TIMEOUT = 15_000;
119119export 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