From 9ec9aef63283c4f96c7867f6620bef9daaa129ca Mon Sep 17 00:00:00 2001
From: Matiss Janis Aboltins <matiss@mja.lv>
Date: Wed, 10 Jul 2024 21:58:20 +0100
Subject: [PATCH] :sparkles: (budget-type) moving the selector to settings page
 (#3017)

* :sparkles: (budget-type) moving the selector to settings page

* Feedback: move the block down
---
 .../src/components/FinancesApp.tsx            |  18 +--
 .../desktop-client/src/components/Modals.tsx  |  11 --
 .../src/components/Titlebar.tsx               | 144 +-----------------
 .../src/components/budget/index.tsx           |  37 +----
 .../src/components/mobile/budget/index.tsx    |  32 +---
 .../components/modals/BudgetPageMenuModal.tsx |  17 ---
 .../modals/SwitchBudgetTypeModal.tsx          |  73 ---------
 .../settings/BudgetTypeSettings.tsx           |  85 +++++++++++
 .../src/components/settings/index.tsx         |   3 +
 .../src/client/state-types/modals.d.ts        |   2 -
 upcoming-release-notes/3017.md                |   6 +
 11 files changed, 107 insertions(+), 321 deletions(-)
 delete mode 100644 packages/desktop-client/src/components/modals/SwitchBudgetTypeModal.tsx
 create mode 100644 packages/desktop-client/src/components/settings/BudgetTypeSettings.tsx
 create mode 100644 upcoming-release-notes/3017.md

diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx
index 2950cf55f..8355491f6 100644
--- a/packages/desktop-client/src/components/FinancesApp.tsx
+++ b/packages/desktop-client/src/components/FinancesApp.tsx
@@ -42,7 +42,7 @@ import { ScrollProvider } from './ScrollProvider';
 import { Settings } from './settings';
 import { FloatableSidebar } from './sidebar';
 import { SidebarProvider } from './sidebar/SidebarProvider';
-import { Titlebar, TitlebarProvider } from './Titlebar';
+import { Titlebar } from './Titlebar';
 
 function NarrowNotSupported({
   redirectTo = '/budget',
@@ -246,15 +246,13 @@ export function FinancesApp() {
 
   return (
     <SpreadsheetProvider>
-      <TitlebarProvider>
-        <SidebarProvider>
-          <BudgetMonthCountProvider>
-            <DndProvider backend={Backend}>
-              <ScrollProvider>{app}</ScrollProvider>
-            </DndProvider>
-          </BudgetMonthCountProvider>
-        </SidebarProvider>
-      </TitlebarProvider>
+      <SidebarProvider>
+        <BudgetMonthCountProvider>
+          <DndProvider backend={Backend}>
+            <ScrollProvider>{app}</ScrollProvider>
+          </DndProvider>
+        </BudgetMonthCountProvider>
+      </SidebarProvider>
     </SpreadsheetProvider>
   );
 }
diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx
index ac5b894b2..bbd54ad98 100644
--- a/packages/desktop-client/src/components/Modals.tsx
+++ b/packages/desktop-client/src/components/Modals.tsx
@@ -53,7 +53,6 @@ import { ScheduledTransactionMenuModal } from './modals/ScheduledTransactionMenu
 import { SelectLinkedAccounts } from './modals/SelectLinkedAccounts';
 import { SimpleFinInitialise } from './modals/SimpleFinInitialise';
 import { SingleInputModal } from './modals/SingleInputModal';
-import { SwitchBudgetTypeModal } from './modals/SwitchBudgetTypeModal';
 import { TransferModal } from './modals/TransferModal';
 import { DiscoverSchedules } from './schedules/DiscoverSchedules';
 import { PostsOfflineNotification } from './schedules/PostsOfflineNotification';
@@ -423,15 +422,6 @@ export function Modals() {
             />
           );
 
-        case 'switch-budget-type':
-          return (
-            <SwitchBudgetTypeModal
-              key={name}
-              modalProps={modalProps}
-              onSwitch={options.onSwitch}
-            />
-          );
-
         case 'account-menu':
           return (
             <AccountMenuModal
@@ -623,7 +613,6 @@ export function Modals() {
               onAddCategoryGroup={options.onAddCategoryGroup}
               onToggleHiddenCategories={options.onToggleHiddenCategories}
               onSwitchBudgetFile={options.onSwitchBudgetFile}
-              onSwitchBudgetType={options.onSwitchBudgetType}
             />
           );
 
diff --git a/packages/desktop-client/src/components/Titlebar.tsx b/packages/desktop-client/src/components/Titlebar.tsx
index 8db6876bc..0a1e76dfb 100644
--- a/packages/desktop-client/src/components/Titlebar.tsx
+++ b/packages/desktop-client/src/components/Titlebar.tsx
@@ -1,11 +1,4 @@
-import React, {
-  createContext,
-  useState,
-  useEffect,
-  useRef,
-  useContext,
-  type ReactNode,
-} from 'react';
+import React, { useState, useEffect } from 'react';
 import { useHotkeys } from 'react-hotkeys-hook';
 import { Routes, Route, useLocation } from 'react-router-dom';
 
@@ -13,10 +6,8 @@ import * as Platform from 'loot-core/src/client/platform';
 import * as queries from 'loot-core/src/client/queries';
 import { listen } from 'loot-core/src/platform/client/fetch';
 import { isDevelopmentEnvironment } from 'loot-core/src/shared/environment';
-import { type LocalPrefs } from 'loot-core/src/types/prefs';
 
 import { useActions } from '../hooks/useActions';
-import { useFeatureFlag } from '../hooks/useFeatureFlag';
 import { useGlobalPref } from '../hooks/useGlobalPref';
 import { useLocalPref } from '../hooks/useLocalPref';
 import { useNavigate } from '../hooks/useNavigate';
@@ -33,10 +24,8 @@ import { theme, type CSSProperties, styles } from '../style';
 import { AccountSyncCheck } from './accounts/AccountSyncCheck';
 import { AnimatedRefresh } from './AnimatedRefresh';
 import { MonthCountSelector } from './budget/MonthCountSelector';
-import { Button, ButtonWithLoading } from './common/Button';
+import { Button } from './common/Button';
 import { Link } from './common/Link';
-import { Paragraph } from './common/Paragraph';
-import { Popover } from './common/Popover';
 import { Text } from './common/Text';
 import { View } from './common/View';
 import { LoggedInUser } from './LoggedInUser';
@@ -45,55 +34,6 @@ import { useSidebar } from './sidebar/SidebarProvider';
 import { useSheetValue } from './spreadsheet/useSheetValue';
 import { ThemeSelector } from './ThemeSelector';
 
-export const SWITCH_BUDGET_MESSAGE_TYPE = 'budget/switch-type';
-
-type SwitchBudgetTypeMessage = {
-  type: typeof SWITCH_BUDGET_MESSAGE_TYPE;
-  payload: {
-    newBudgetType: LocalPrefs['budgetType'];
-  };
-};
-export type TitlebarMessage = SwitchBudgetTypeMessage;
-
-type Listener = (msg: TitlebarMessage) => void;
-export type TitlebarContextValue = {
-  sendEvent: (msg: TitlebarMessage) => void;
-  subscribe: (listener: Listener) => () => void;
-};
-
-export const TitlebarContext = createContext<TitlebarContextValue>({
-  sendEvent() {
-    throw new Error('TitlebarContext not initialized');
-  },
-  subscribe() {
-    throw new Error('TitlebarContext not initialized');
-  },
-});
-
-type TitlebarProviderProps = {
-  children?: ReactNode;
-};
-
-export function TitlebarProvider({ children }: TitlebarProviderProps) {
-  const listeners = useRef<Listener[]>([]);
-
-  function sendEvent(msg: TitlebarMessage) {
-    listeners.current.forEach(func => func(msg));
-  }
-
-  function subscribe(listener: Listener) {
-    listeners.current.push(listener);
-    return () =>
-      (listeners.current = listeners.current.filter(func => func !== listener));
-  }
-
-  return (
-    <TitlebarContext.Provider value={{ sendEvent, subscribe }}>
-      {children}
-    </TitlebarContext.Provider>
-  );
-}
-
 function UncategorizedButton() {
   const count: number | null = useSheetValue(queries.uncategorizedCount());
   if (count === null || count <= 0) {
@@ -287,31 +227,6 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
 
 function BudgetTitlebar() {
   const [maxMonths, setMaxMonthsPref] = useGlobalPref('maxMonths');
-  const [budgetType] = useLocalPref('budgetType');
-  const { sendEvent } = useContext(TitlebarContext);
-
-  const [loading, setLoading] = useState(false);
-  const [showPopover, setShowPopover] = useState(false);
-  const triggerRef = useRef(null);
-
-  const reportBudgetEnabled = useFeatureFlag('reportBudget');
-
-  function onSwitchType() {
-    setLoading(true);
-    if (!loading) {
-      const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
-      sendEvent({
-        type: SWITCH_BUDGET_MESSAGE_TYPE,
-        payload: {
-          newBudgetType,
-        },
-      });
-    }
-  }
-
-  useEffect(() => {
-    setLoading(false);
-  }, [budgetType]);
 
   return (
     <View style={{ flexDirection: 'row', alignItems: 'center' }}>
@@ -319,61 +234,6 @@ function BudgetTitlebar() {
         maxMonths={maxMonths || 1}
         onChange={value => setMaxMonthsPref(value)}
       />
-      {reportBudgetEnabled && (
-        <View style={{ marginLeft: -5 }}>
-          <ButtonWithLoading
-            ref={triggerRef}
-            type="bare"
-            loading={loading}
-            style={{
-              alignSelf: 'flex-start',
-              padding: '4px 7px',
-            }}
-            title="Learn more about budgeting"
-            onClick={() => setShowPopover(true)}
-          >
-            {budgetType === 'report' ? 'Report budget' : 'Rollover budget'}
-          </ButtonWithLoading>
-
-          <Popover
-            triggerRef={triggerRef}
-            placement="bottom start"
-            isOpen={showPopover}
-            onOpenChange={() => setShowPopover(false)}
-            style={{
-              padding: 10,
-              maxWidth: 400,
-            }}
-          >
-            <Paragraph>
-              You are currently using a{' '}
-              <Text style={{ fontWeight: 600 }}>
-                {budgetType === 'report' ? 'Report budget' : 'Rollover budget'}.
-              </Text>{' '}
-              Switching will not lose any data and you can always switch back.
-            </Paragraph>
-            <Paragraph>
-              <ButtonWithLoading
-                type="primary"
-                loading={loading}
-                onClick={onSwitchType}
-              >
-                Switch to a{' '}
-                {budgetType === 'report' ? 'Rollover budget' : 'Report budget'}
-              </ButtonWithLoading>
-            </Paragraph>
-            <Paragraph isLast={true}>
-              <Link
-                variant="external"
-                to="https://actualbudget.org/docs/experimental/report-budget"
-                linkColor="muted"
-              >
-                How do these types of budgeting work?
-              </Link>
-            </Paragraph>
-          </Popover>
-        </View>
-      )}
     </View>
   );
 }
diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx
index 056d240b2..16c0211db 100644
--- a/packages/desktop-client/src/components/budget/index.tsx
+++ b/packages/desktop-client/src/components/budget/index.tsx
@@ -1,5 +1,5 @@
 // @ts-strict-ignore
-import React, { memo, useContext, useMemo, useState, useEffect } from 'react';
+import React, { memo, useMemo, useState, useEffect } from 'react';
 import { useDispatch } from 'react-redux';
 
 import {
@@ -10,7 +10,6 @@ import {
   deleteCategory,
   deleteGroup,
   getCategories,
-  loadPrefs,
   moveCategory,
   moveCategoryGroup,
   pushModal,
@@ -28,19 +27,13 @@ import { useNavigate } from '../../hooks/useNavigate';
 import { styles } from '../../style';
 import { View } from '../common/View';
 import { NamespaceContext } from '../spreadsheet/NamespaceContext';
-import {
-  SWITCH_BUDGET_MESSAGE_TYPE,
-  TitlebarContext,
-  type TitlebarContextValue,
-  type TitlebarMessage,
-} from '../Titlebar';
 
 import { DynamicBudgetTable } from './DynamicBudgetTable';
 import * as report from './report/ReportComponents';
 import { ReportProvider } from './report/ReportContext';
 import * as rollover from './rollover/RolloverComponents';
 import { RolloverProvider } from './rollover/RolloverContext';
-import { prewarmAllMonths, prewarmMonth, switchBudgetType } from './util';
+import { prewarmAllMonths, prewarmMonth } from './util';
 
 type ReportComponents = {
   SummaryComponent: typeof report.BudgetSummary;
@@ -66,7 +59,6 @@ type BudgetInnerProps = {
   accountId?: string;
   reportComponents: ReportComponents;
   rolloverComponents: RolloverComponents;
-  titlebar: TitlebarContextValue;
 };
 
 function BudgetInner(props: BudgetInnerProps) {
@@ -95,8 +87,6 @@ function BudgetInner(props: BudgetInnerProps) {
   }
 
   useEffect(() => {
-    const { titlebar } = props;
-
     async function run() {
       loadCategories();
 
@@ -132,8 +122,6 @@ function BudgetInner(props: BudgetInnerProps) {
           loadCategories();
         }
       }),
-
-      titlebar.subscribe(onTitlebarEvent),
     ];
 
     return () => {
@@ -323,24 +311,6 @@ function BudgetInner(props: BudgetInnerProps) {
     setSummaryCollapsedPref(!summaryCollapsed);
   };
 
-  const onTitlebarEvent = async ({ type, payload }: TitlebarMessage) => {
-    switch (type) {
-      case SWITCH_BUDGET_MESSAGE_TYPE: {
-        await switchBudgetType(
-          payload.newBudgetType,
-          spreadsheet,
-          bounds,
-          startMonth,
-          async () => {
-            dispatch(loadPrefs());
-          },
-        );
-        break;
-      }
-      default:
-    }
-  };
-
   const { reportComponents, rolloverComponents } = props;
 
   if (!initialized || !categoryGroups) {
@@ -416,8 +386,6 @@ const RolloverBudgetSummary = memo<{ month: string }>(props => {
 RolloverBudgetSummary.displayName = 'RolloverBudgetSummary';
 
 export function Budget() {
-  const titlebar = useContext(TitlebarContext);
-
   const reportComponents = useMemo<ReportComponents>(
     () => ({
       SummaryComponent: report.BudgetSummary,
@@ -460,7 +428,6 @@ export function Budget() {
       <BudgetInner
         reportComponents={reportComponents}
         rolloverComponents={rolloverComponents}
-        titlebar={titlebar}
       />
     </View>
   );
diff --git a/packages/desktop-client/src/components/mobile/budget/index.tsx b/packages/desktop-client/src/components/mobile/budget/index.tsx
index 2cd7a522a..9fcc4ab9a 100644
--- a/packages/desktop-client/src/components/mobile/budget/index.tsx
+++ b/packages/desktop-client/src/components/mobile/budget/index.tsx
@@ -16,7 +16,6 @@ import {
   updateCategory,
   updateGroup,
   sync,
-  loadPrefs,
 } from 'loot-core/client/actions';
 import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
 import { send, listen } from 'loot-core/src/platform/client/fetch';
@@ -31,7 +30,7 @@ import { useLocalPref } from '../../../hooks/useLocalPref';
 import { useSetThemeColor } from '../../../hooks/useSetThemeColor';
 import { AnimatedLoading } from '../../../icons/AnimatedLoading';
 import { theme } from '../../../style';
-import { prewarmMonth, switchBudgetType } from '../../budget/util';
+import { prewarmMonth } from '../../budget/util';
 import { View } from '../../common/View';
 import { NamespaceContext } from '../../spreadsheet/NamespaceContext';
 import { SyncRefresh } from '../../SyncRefresh';
@@ -289,23 +288,6 @@ function BudgetInner(props: BudgetInnerProps) {
   //   );
   // };
 
-  const onSwitchBudgetType = async () => {
-    setInitialized(false);
-
-    const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
-    await switchBudgetType(
-      newBudgetType,
-      spreadsheet,
-      bounds,
-      startMonth,
-      async () => {
-        dispatch(loadPrefs());
-      },
-    );
-
-    setInitialized(true);
-  };
-
   const onSaveNotes = async (id, notes) => {
     await send('notes-save', { id, note: notes });
   };
@@ -358,17 +340,6 @@ function BudgetInner(props: BudgetInnerProps) {
     );
   };
 
-  const onOpenSwitchBudgetTypeModal = () => {
-    dispatch(
-      pushModal('switch-budget-type', {
-        onSwitch: () => {
-          onSwitchBudgetType();
-          dispatch(collapseModals('budget-page-menu'));
-        },
-      }),
-    );
-  };
-
   const [showHiddenCategories, setShowHiddenCategoriesPref] = useLocalPref(
     'budget.showHiddenCategories',
   );
@@ -408,7 +379,6 @@ function BudgetInner(props: BudgetInnerProps) {
         onAddCategoryGroup: onOpenNewCategoryGroupModal,
         onToggleHiddenCategories,
         onSwitchBudgetFile,
-        onSwitchBudgetType: onOpenSwitchBudgetTypeModal,
       }),
     );
   };
diff --git a/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx b/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx
index 2e3d06efb..b6b164e52 100644
--- a/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx
+++ b/packages/desktop-client/src/components/modals/BudgetPageMenuModal.tsx
@@ -1,6 +1,5 @@
 import React, { type ComponentPropsWithoutRef } from 'react';
 
-import { useFeatureFlag } from '../../hooks/useFeatureFlag';
 import { useLocalPref } from '../../hooks/useLocalPref';
 import { type CSSProperties, theme, styles } from '../../style';
 import { Menu } from '../common/Menu';
@@ -18,7 +17,6 @@ export function BudgetPageMenuModal({
   onAddCategoryGroup,
   onToggleHiddenCategories,
   onSwitchBudgetFile,
-  onSwitchBudgetType,
 }: BudgetPageMenuModalProps) {
   const defaultMenuItemStyle: CSSProperties = {
     ...styles.mobileMenuItem,
@@ -34,7 +32,6 @@ export function BudgetPageMenuModal({
         onAddCategoryGroup={onAddCategoryGroup}
         onToggleHiddenCategories={onToggleHiddenCategories}
         onSwitchBudgetFile={onSwitchBudgetFile}
-        onSwitchBudgetType={onSwitchBudgetType}
       />
     </Modal>
   );
@@ -47,17 +44,14 @@ type BudgetPageMenuProps = Omit<
   onAddCategoryGroup: () => void;
   onToggleHiddenCategories: () => void;
   onSwitchBudgetFile: () => void;
-  onSwitchBudgetType: () => void;
 };
 
 function BudgetPageMenu({
   onAddCategoryGroup,
   onToggleHiddenCategories,
   onSwitchBudgetFile,
-  onSwitchBudgetType,
   ...props
 }: BudgetPageMenuProps) {
-  const isReportBudgetEnabled = useFeatureFlag('reportBudget');
   const [showHiddenCategories] = useLocalPref('budget.showHiddenCategories');
 
   const onMenuSelect = (name: string) => {
@@ -74,9 +68,6 @@ function BudgetPageMenu({
       case 'switch-budget-file':
         onSwitchBudgetFile?.();
         break;
-      case 'switch-budget-type':
-        onSwitchBudgetType?.();
-        break;
       default:
         throw new Error(`Unrecognized menu item: ${name}`);
     }
@@ -99,14 +90,6 @@ function BudgetPageMenu({
           name: 'switch-budget-file',
           text: 'Switch budget file',
         },
-        ...(isReportBudgetEnabled
-          ? [
-              {
-                name: 'switch-budget-type',
-                text: 'Switch budget type',
-              },
-            ]
-          : []),
       ]}
     />
   );
diff --git a/packages/desktop-client/src/components/modals/SwitchBudgetTypeModal.tsx b/packages/desktop-client/src/components/modals/SwitchBudgetTypeModal.tsx
deleted file mode 100644
index e4b0c0b49..000000000
--- a/packages/desktop-client/src/components/modals/SwitchBudgetTypeModal.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-// @ts-strict-ignore
-import React from 'react';
-
-import { useLocalPref } from '../../hooks/useLocalPref';
-import { useResponsive } from '../../ResponsiveProvider';
-import { styles } from '../../style';
-import { Button } from '../common/Button';
-import { Link } from '../common/Link';
-import { Modal, ModalTitle } from '../common/Modal';
-import { Paragraph } from '../common/Paragraph';
-import { Text } from '../common/Text';
-import { type CommonModalProps } from '../Modals';
-
-type SwitchBudgetTypeModalProps = {
-  modalProps: CommonModalProps;
-  onSwitch: () => void;
-};
-
-export function SwitchBudgetTypeModal({
-  modalProps,
-  onSwitch,
-}: SwitchBudgetTypeModalProps) {
-  const [budgetType] = useLocalPref('budgetType');
-  const { isNarrowWidth } = useResponsive();
-  const narrowStyle = isNarrowWidth
-    ? {
-        height: styles.mobileMinHeight,
-      }
-    : {};
-  return (
-    <Modal
-      title={<ModalTitle title="Switch budget type?" shrinkOnOverflow />}
-      {...modalProps}
-    >
-      <>
-        <Paragraph>
-          You are currently using a{' '}
-          <Text style={{ fontWeight: 600 }}>
-            {budgetType === 'report' ? 'Report budget' : 'Rollover budget'}.
-          </Text>{' '}
-          Switching will not lose any data and you can always switch back.
-        </Paragraph>
-        <Button
-          type="primary"
-          style={{
-            ...narrowStyle,
-          }}
-          onClick={() => {
-            onSwitch?.();
-            modalProps.onClose?.();
-          }}
-        >
-          Switch to a{' '}
-          {budgetType === 'report' ? 'Rollover budget' : 'Report budget'}
-        </Button>
-        <Paragraph
-          isLast={true}
-          style={{
-            marginTop: 10,
-          }}
-        >
-          <Link
-            variant="external"
-            to="https://actualbudget.org/docs/experimental/report-budget"
-            linkColor="muted"
-          >
-            How do these types of budgeting work?
-          </Link>
-        </Paragraph>
-      </>
-    </Modal>
-  );
-}
diff --git a/packages/desktop-client/src/components/settings/BudgetTypeSettings.tsx b/packages/desktop-client/src/components/settings/BudgetTypeSettings.tsx
new file mode 100644
index 000000000..e98d0ea3a
--- /dev/null
+++ b/packages/desktop-client/src/components/settings/BudgetTypeSettings.tsx
@@ -0,0 +1,85 @@
+import React, { useState } from 'react';
+import { useDispatch } from 'react-redux';
+
+import { loadPrefs } from 'loot-core/src/client/actions';
+import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
+import * as monthUtils from 'loot-core/src/shared/months';
+
+import { useLocalPref } from '../../hooks/useLocalPref';
+import { switchBudgetType } from '../budget/util';
+import { ButtonWithLoading } from '../common/Button2';
+import { Link } from '../common/Link';
+import { Text } from '../common/Text';
+
+import { Setting } from './UI';
+
+export function BudgetTypeSettings() {
+  const dispatch = useDispatch();
+  const [budgetType] = useLocalPref('budgetType');
+  const [loading, setLoading] = useState(false);
+
+  const currentMonth = monthUtils.currentMonth();
+  const [startMonthPref] = useLocalPref('budget.startMonth');
+  const startMonth = startMonthPref || currentMonth;
+  const spreadsheet = useSpreadsheet();
+
+  function onSwitchType() {
+    setLoading(true);
+
+    if (!loading) {
+      const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
+
+      switchBudgetType(
+        newBudgetType,
+        spreadsheet,
+        {
+          start: startMonth,
+          end: startMonth,
+        },
+        startMonth,
+        async () => {
+          dispatch(loadPrefs());
+          setLoading(false);
+        },
+      );
+    }
+  }
+
+  return (
+    <Setting
+      primaryAction={
+        <ButtonWithLoading isLoading={loading} onPress={onSwitchType}>
+          Switch to {budgetType === 'report' ? 'envelope' : 'tracking'}{' '}
+          budgeting
+        </ButtonWithLoading>
+      }
+    >
+      <Text>
+        <strong>Envelope budgeting</strong> (recommended) digitally mimics
+        physical envelope budgeting system by allocating funds into virtual
+        envelopes for different expenses. It helps track spending and ensure you
+        don‘t overspend in any category.{' '}
+        <Link
+          variant="external"
+          to="https://actualbudget.org/docs/getting-started/envelope-budgeting"
+          linkColor="purple"
+        >
+          Learn more…
+        </Link>
+      </Text>
+      <Text>
+        With <strong>tracking budgeting</strong>, category balances reset each
+        month, and funds are managed using a “Saved” metric instead of “To Be
+        Budgeted.” Income is forecasted to plan future spending, rather than
+        relying on current available funds.{' '}
+        <Link
+          variant="external"
+          to="https://actualbudget.org/docs/experimental/report-budget"
+          linkColor="purple"
+        >
+          Learn more…
+        </Link>
+      </Text>
+    </Setting>
+  );
+}
diff --git a/packages/desktop-client/src/components/settings/index.tsx b/packages/desktop-client/src/components/settings/index.tsx
index 3da251d76..bed854ed8 100644
--- a/packages/desktop-client/src/components/settings/index.tsx
+++ b/packages/desktop-client/src/components/settings/index.tsx
@@ -6,6 +6,7 @@ import * as Platform from 'loot-core/src/client/platform';
 import { listen } from 'loot-core/src/platform/client/fetch';
 
 import { useActions } from '../../hooks/useActions';
+import { useFeatureFlag } from '../../hooks/useFeatureFlag';
 import { useGlobalPref } from '../../hooks/useGlobalPref';
 import { useLatestVersion, useIsOutdated } from '../../hooks/useLatestVersion';
 import { useLocalPref } from '../../hooks/useLocalPref';
@@ -23,6 +24,7 @@ import { MOBILE_NAV_HEIGHT } from '../mobile/MobileNavTabs';
 import { Page } from '../Page';
 import { useServerVersion } from '../ServerContext';
 
+import { BudgetTypeSettings } from './BudgetTypeSettings';
 import { EncryptionSettings } from './Encryption';
 import { ExperimentalFeatures } from './Experimental';
 import { ExportBudget } from './Export';
@@ -178,6 +180,7 @@ export function Settings() {
         <ThemeSettings />
         <FormatSettings />
         <EncryptionSettings />
+        {useFeatureFlag('reportBudget') && <BudgetTypeSettings />}
         <ExportBudget />
 
         <AdvancedToggle>
diff --git a/packages/loot-core/src/client/state-types/modals.d.ts b/packages/loot-core/src/client/state-types/modals.d.ts
index 3c091e1cc..3c6b4ab85 100644
--- a/packages/loot-core/src/client/state-types/modals.d.ts
+++ b/packages/loot-core/src/client/state-types/modals.d.ts
@@ -129,7 +129,6 @@ type FinanceModals = {
   'schedules-discover': null;
 
   'schedule-posts-offline-notification': null;
-  'switch-budget-type': { onSwitch: () => void };
   'account-menu': {
     accountId: string;
     onSave: (account: AccountEntity) => void;
@@ -237,7 +236,6 @@ type FinanceModals = {
     onAddCategoryGroup: () => void;
     onToggleHiddenCategories: () => void;
     onSwitchBudgetFile: () => void;
-    onSwitchBudgetType: () => void;
   };
   'rollover-budget-month-menu': {
     month: string;
diff --git a/upcoming-release-notes/3017.md b/upcoming-release-notes/3017.md
new file mode 100644
index 000000000..6d320b21a
--- /dev/null
+++ b/upcoming-release-notes/3017.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [MatissJanis]
+---
+
+Moved budget type toggle to the settings page
-- 
GitLab