diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-1-chromium-linux.png index dd2abcee93d..66afa1a7866 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-2-chromium-linux.png index 443581d98f5..e50ea3fe2f2 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png index 465ea69cb5f..a29cc002d92 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png index 900a67f119f..43a9bdd4c2b 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/page-models/reports-page.js b/packages/desktop-client/e2e/page-models/reports-page.js index 5ebbc428454..1eb33f1fa3d 100644 --- a/packages/desktop-client/e2e/page-models/reports-page.js +++ b/packages/desktop-client/e2e/page-models/reports-page.js @@ -8,6 +8,16 @@ export class ReportsPage { return this.pageContent.getByRole('link', { name: /^Net/ }).waitFor(); } + async goToNetWorthPage() { + await this.pageContent.getByRole('link', { name: /^Net/ }).click(); + return new ReportsPage(this.page); + } + + async goToCashFlowPage() { + await this.pageContent.getByRole('link', { name: /^Cash/ }).click(); + return new ReportsPage(this.page); + } + async getAvailableReportList() { return this.pageContent .getByRole('link') diff --git a/packages/desktop-client/e2e/reports.test.js b/packages/desktop-client/e2e/reports.test.js index 6a0763223b6..54db8e5c48d 100644 --- a/packages/desktop-client/e2e/reports.test.js +++ b/packages/desktop-client/e2e/reports.test.js @@ -34,4 +34,14 @@ test.describe('Reports', () => { expect(reports).toEqual(['Net Worth', 'Cash Flow']); await expect(page).toHaveScreenshot(screenshotConfig(page)); }); + + test('loads net worth graph and checks visuals', async () => { + await reportsPage.goToNetWorthPage(); + await expect(page).toHaveScreenshot(screenshotConfig(page)); + }); + + test('loads cash flow graph and checks visuals', async () => { + await reportsPage.goToCashFlowPage(); + await expect(page).toHaveScreenshot(screenshotConfig(page)); + }); }); diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png new file mode 100644 index 00000000000..ebc5ed1f828 Binary files /dev/null and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-1-chromium-linux.png new file mode 100644 index 00000000000..7549e2cf27c Binary files /dev/null and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index e2b86fe6b52..5bd8879c351 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -74,7 +74,7 @@ "watch": "cross-env BROWSER=none yarn start", "build": "cross-env INLINE_RUNTIME_CHUNK=false craco build", "build:browser": "cross-env ./bin/build-browser", - "generate:icons": "rm src/icons/*/*.js; cd src/icons && svgr --expand-props start --ext js -d . .", + "generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --typescript --expand-props start -d . .", "test": "craco test", "e2e": "npx playwright test --browser=chromium", "vrt": "cross-env VRT=true npx playwright test --browser=chromium" diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx index c3e82793c0b..58792c219c6 100644 --- a/packages/desktop-client/src/components/FinancesApp.tsx +++ b/packages/desktop-client/src/components/FinancesApp.tsx @@ -5,7 +5,6 @@ import { Route, Routes, Navigate, - NavLink, useNavigate, BrowserRouter, useLocation, @@ -21,12 +20,8 @@ import checkForUpdateNotification from 'loot-core/src/client/update-notification import * as undo from 'loot-core/src/platform/client/undo'; import { useActions } from '../hooks/useActions'; -import Add from '../icons/v1/Add'; -import Cog from '../icons/v1/Cog'; -import PiggyBank from '../icons/v1/PiggyBank'; -import Wallet from '../icons/v1/Wallet'; import { useResponsive } from '../ResponsiveProvider'; -import { theme, styles } from '../style'; +import { theme } from '../style'; import { ExposeNavigate } from '../util/router-tools'; import { getIsOutdated, getLatestVersion } from '../util/versions'; @@ -35,11 +30,13 @@ import { BudgetMonthCountProvider } from './budget/BudgetMonthCountContext'; import View from './common/View'; import GlobalKeys from './GlobalKeys'; import { ManageRulesPage } from './ManageRulesPage'; +import MobileNavTabs from './mobile/MobileNavTabs'; import Modals from './Modals'; import Notifications from './Notifications'; import { ManagePayeesPage } from './payees/ManagePayeesPage'; import Reports from './reports'; import { NarrowAlternate, WideComponent } from './responsive'; +import ScrollProvider from './ScrollProvider'; import Settings from './settings'; import FloatableSidebar, { SidebarProvider } from './sidebar'; import Titlebar, { TitlebarProvider } from './Titlebar'; @@ -73,48 +70,6 @@ function WideNotSupported({ children, redirectTo = '/budget' }) { return isNarrowWidth ? children : null; } -function NavTab({ icon: TabIcon, name, path }) { - return ( - ({ - alignItems: 'center', - color: isActive ? theme.mobileNavItemSelected : theme.mobileNavItem, - display: 'flex', - flexDirection: 'column', - textDecoration: 'none', - })} - > - - {name} - - ); -} - -function MobileNavTabs() { - const { isNarrowWidth } = useResponsive(); - return ( -
- - - - -
- ); -} - function RouterBehaviors({ getAccounts }) { let navigate = useNavigate(); useEffect(() => { @@ -305,7 +260,9 @@ export default function FinancesAppWithContext() { - {app} + + {app} + diff --git a/packages/desktop-client/src/components/GlobalKeys.tsx b/packages/desktop-client/src/components/GlobalKeys.tsx index a75c0763509..cff3d38ae2f 100644 --- a/packages/desktop-client/src/components/GlobalKeys.tsx +++ b/packages/desktop-client/src/components/GlobalKeys.tsx @@ -1,8 +1,9 @@ import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; import * as Platform from 'loot-core/src/client/platform'; +import useNavigate from '../hooks/useNavigate'; + export default function GlobalKeys() { let navigate = useNavigate(); useEffect(() => { diff --git a/packages/desktop-client/src/components/LoggedInUser.tsx b/packages/desktop-client/src/components/LoggedInUser.tsx index f1775be3410..0459cc9062b 100644 --- a/packages/desktop-client/src/components/LoggedInUser.tsx +++ b/packages/desktop-client/src/components/LoggedInUser.tsx @@ -78,7 +78,7 @@ export default function LoggedInUser({ return ( >; +}; + +function ManageRulesContent({ + isModal, + payeeId, + setLoading, +}: ManageRulesContentProps) { let [allRules, setAllRules] = useState(null); let [rules, setRules] = useState(null); let [filter, setFilter] = useState(''); @@ -191,7 +209,7 @@ function ManageRulesContent({ isModal, payeeId, setLoading }) { }, []); function onCreateRule() { - let rule = { + let rule: RuleEntity = { stage: null, conditionsOp: 'and', conditions: [ @@ -314,11 +332,17 @@ function ManageRulesContent({ isModal, payeeId, setLoading }) { ); } +type ManageRulesProps = { + isModal: boolean; + payeeId: string | null; + setLoading?: Dispatch>; +}; + export default function ManageRules({ isModal, payeeId, setLoading = () => {}, -}) { +}: ManageRulesProps) { return ( @@ -129,7 +129,7 @@ function Notification({ positive ? theme.noticeBorder : error - ? theme.altErrorAccent + ? theme.errorBorder : theme.altWarningAccent }`, ...styles.shadowLarge, @@ -175,7 +175,7 @@ function Notification({ positive ? theme.noticeBorder : error - ? theme.altErrorAccent + ? theme.errorBorder : theme.altWarningAccent }`, color: 'currentColor', @@ -185,7 +185,7 @@ function Notification({ backgroundColor: positive ? theme.noticeBackground : error - ? theme.altErrorBackground + ? theme.errorBackground : theme.altWarningBackground, }, }} diff --git a/packages/desktop-client/src/components/ScrollProvider.tsx b/packages/desktop-client/src/components/ScrollProvider.tsx new file mode 100644 index 00000000000..06e59fb60e1 --- /dev/null +++ b/packages/desktop-client/src/components/ScrollProvider.tsx @@ -0,0 +1,54 @@ +import React, { + type ReactNode, + createContext, + useState, + useContext, + useEffect, +} from 'react'; + +import debounce from 'debounce'; + +type IScrollContext = { + scrollY: number | undefined; + isBottomReached: boolean; +}; + +const ScrollContext = createContext(undefined); + +type ScrollProviderProps = { + children?: ReactNode; +}; + +export default function ScrollProvider({ children }: ScrollProviderProps) { + const [scrollY, setScrollY] = useState(undefined); + const [isBottomReached, setIsBottomReached] = useState(false); + + useEffect(() => { + const listenToScroll = debounce(e => { + setScrollY(e.target?.scrollTop || 0); + setIsBottomReached( + e.target?.scrollHeight - e.target?.scrollTop <= e.target?.clientHeight, + ); + }, 20); + + window.addEventListener('scroll', listenToScroll, { + capture: true, + passive: true, + }); + return () => + window.removeEventListener('scroll', listenToScroll, { + capture: true, + }); + }, []); + + return ( + + ); +} + +export function useScroll(): IScrollContext { + return useContext(ScrollContext); +} diff --git a/packages/desktop-client/src/components/Titlebar.tsx b/packages/desktop-client/src/components/Titlebar.tsx index 8b0e593a1e5..bab3123ba3f 100644 --- a/packages/desktop-client/src/components/Titlebar.tsx +++ b/packages/desktop-client/src/components/Titlebar.tsx @@ -7,7 +7,7 @@ import React, { type ReactNode, } from 'react'; import { useSelector } from 'react-redux'; -import { Routes, Route, useLocation, useNavigate } from 'react-router-dom'; +import { Routes, Route, useLocation } from 'react-router-dom'; import * as Platform from 'loot-core/src/client/platform'; import * as queries from 'loot-core/src/client/queries'; @@ -15,6 +15,7 @@ import { listen } from 'loot-core/src/platform/client/fetch'; import { useActions } from '../hooks/useActions'; import useFeatureFlag from '../hooks/useFeatureFlag'; +import useNavigate from '../hooks/useNavigate'; import ArrowLeft from '../icons/v1/ArrowLeft'; import AlertTriangle from '../icons/v2/AlertTriangle'; import SvgEye from '../icons/v2/Eye'; @@ -152,7 +153,7 @@ export function SyncButton({ style, isMobile = false }: SyncButtonProps) { const mobileColor = syncState === 'error' - ? theme.alt4ErrorText + ? theme.errorText : syncState === 'disabled' || syncState === 'offline' || syncState === 'local' @@ -160,7 +161,7 @@ export function SyncButton({ style, isMobile = false }: SyncButtonProps) { : style.color; const desktopColor = syncState === 'error' - ? theme.alt2ErrorText + ? theme.errorTextDark : syncState === 'disabled' || syncState === 'offline' || syncState === 'local' diff --git a/packages/desktop-client/src/components/UpdateNotification.tsx b/packages/desktop-client/src/components/UpdateNotification.tsx index 7e844e02999..95b4051adee 100644 --- a/packages/desktop-client/src/components/UpdateNotification.tsx +++ b/packages/desktop-client/src/components/UpdateNotification.tsx @@ -36,7 +36,7 @@ export default function UpdateNotification() { bottom: 0, right: 0, margin: '15px 17px', - backgroundColor: theme.altPageTextPositive, + backgroundColor: theme.pageTextPositive, color: theme.tableBackground, padding: '7px 10px', borderRadius: 4, diff --git a/packages/desktop-client/src/components/accounts/MobileAccount.js b/packages/desktop-client/src/components/accounts/MobileAccount.js index 83a3f6c5b14..fcf3a487e41 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccount.js +++ b/packages/desktop-client/src/components/accounts/MobileAccount.js @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useParams, useNavigate } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import debounce from 'debounce'; import memoizeOne from 'memoize-one'; @@ -20,6 +20,7 @@ import { } from 'loot-core/src/shared/transactions'; import useCategories from '../../hooks/useCategories'; +import useNavigate from '../../hooks/useNavigate'; import { useSetThemeColor } from '../../hooks/useSetThemeColor'; import { theme } from '../../style'; diff --git a/packages/desktop-client/src/components/accounts/MobileAccounts.js b/packages/desktop-client/src/components/accounts/MobileAccounts.js index 573bc9040b0..09364b154e8 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccounts.js +++ b/packages/desktop-client/src/components/accounts/MobileAccounts.js @@ -1,11 +1,11 @@ import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; import * as queries from 'loot-core/src/client/queries'; import { useActions } from '../../hooks/useActions'; import useCategories from '../../hooks/useCategories'; +import useNavigate from '../../hooks/useNavigate'; import { useSetThemeColor } from '../../hooks/useSetThemeColor'; import { theme, styles } from '../../style'; import Button from '../common/Button'; @@ -23,7 +23,7 @@ function AccountHeader({ name, amount, style = {} }) { flex: '1 0 auto', flexDirection: 'row', marginTop: 10, - color: theme.altpageTextSubdued, + color: theme.pageTextLight, ...style, }} > @@ -143,7 +143,7 @@ function EmptyMessage({ onAdd }) { Add Account - + In the future, you can add accounts using the add button in the header. diff --git a/packages/desktop-client/src/components/alerts.tsx b/packages/desktop-client/src/components/alerts.tsx index f7a67e4f5c1..464922f28af 100644 --- a/packages/desktop-client/src/components/alerts.tsx +++ b/packages/desktop-client/src/components/alerts.tsx @@ -93,8 +93,8 @@ export const Error = ({ style, children }: ScopedAlertProps) => { return ( {children} diff --git a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.js b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.js index 9f2a8cd41ad..1f2cb6abdfa 100644 --- a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.js +++ b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.js @@ -186,7 +186,7 @@ function PayeeList({ style={{ fontSize: isNarrowWidth ? 'inherit' : 11, padding: 5, - color: theme.altpageTextSubdued, + color: theme.pageTextLight, textAlign: 'center', }} > diff --git a/packages/desktop-client/src/components/budget/BudgetTotals.js b/packages/desktop-client/src/components/budget/BudgetTotals.js index 0f66ddc40e1..5f2fa3079ff 100644 --- a/packages/desktop-client/src/components/budget/BudgetTotals.js +++ b/packages/desktop-client/src/components/budget/BudgetTotals.js @@ -56,7 +56,7 @@ const BudgetTotals = memo(function BudgetTotals({ {menuOpen && ( diff --git a/packages/desktop-client/src/components/budget/MonthCountSelector.tsx b/packages/desktop-client/src/components/budget/MonthCountSelector.tsx index d3baecf087b..25137630cc5 100644 --- a/packages/desktop-client/src/components/budget/MonthCountSelector.tsx +++ b/packages/desktop-client/src/components/budget/MonthCountSelector.tsx @@ -42,9 +42,7 @@ export function MonthCountSelector({ calendars.push( = i ? theme.altpageTextSubdued : theme.altButtonBareText - } + color={maxMonths >= i ? theme.pageTextLight : theme.altButtonBareText} onClick={() => onChange(i)} />, ); diff --git a/packages/desktop-client/src/components/budget/index.js b/packages/desktop-client/src/components/budget/index.js index 68a67017f91..65f984d2a08 100644 --- a/packages/desktop-client/src/components/budget/index.js +++ b/packages/desktop-client/src/components/budget/index.js @@ -7,7 +7,7 @@ import React, { useRef, } from 'react'; import { useSelector } from 'react-redux'; -import { useLocation, useMatch, useNavigate } from 'react-router-dom'; +import { useLocation, useMatch } from 'react-router-dom'; import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider'; import { send, listen } from 'loot-core/src/platform/client/fetch'; @@ -26,6 +26,7 @@ import * as monthUtils from 'loot-core/src/shared/months'; import { useActions } from '../../hooks/useActions'; import useCategories from '../../hooks/useCategories'; import useFeatureFlag from '../../hooks/useFeatureFlag'; +import useNavigate from '../../hooks/useNavigate'; import { styles } from '../../style'; import View from '../common/View'; import { TitlebarContext } from '../Titlebar'; diff --git a/packages/desktop-client/src/components/budget/report/BudgetSummary.tsx b/packages/desktop-client/src/components/budget/report/BudgetSummary.tsx index 8df2bb776ff..816f2dc7633 100644 --- a/packages/desktop-client/src/components/budget/report/BudgetSummary.tsx +++ b/packages/desktop-client/src/components/budget/report/BudgetSummary.tsx @@ -282,7 +282,7 @@ function Saved({ projected, style }: SavedProps) { color: projected ? theme.alt2WarningText : isNegative - ? theme.alt2ErrorText + ? theme.errorTextDark : theme.altUpcomingText, }, ])}`} @@ -407,7 +407,7 @@ export function BudgetSummary({ month }: BudgetSummaryProps) { {menuOpen && ( diff --git a/packages/desktop-client/src/components/budget/rollover/BudgetSummary.tsx b/packages/desktop-client/src/components/budget/rollover/BudgetSummary.tsx index 99e5f7621df..9959da029f5 100644 --- a/packages/desktop-client/src/components/budget/rollover/BudgetSummary.tsx +++ b/packages/desktop-client/src/components/budget/rollover/BudgetSummary.tsx @@ -190,7 +190,7 @@ function ToBudget({ borderBottom: '1px solid transparent', ':hover': { borderColor: isNegative - ? theme.errorText + ? theme.errorBorder : theme.pageTextPositive, }, }, diff --git a/packages/desktop-client/src/components/budget/util.js b/packages/desktop-client/src/components/budget/util.ts similarity index 72% rename from packages/desktop-client/src/components/budget/util.js rename to packages/desktop-client/src/components/budget/util.ts index db5f7e76895..f8c4af733a1 100644 --- a/packages/desktop-client/src/components/budget/util.js +++ b/packages/desktop-client/src/components/budget/util.ts @@ -1,6 +1,9 @@ +import { type CategoryGroupEntity } from 'loot-core/src/types/models'; + import { styles, theme } from '../../style'; +import { type DropPosition } from '../sort'; -export function addToBeBudgetedGroup(groups) { +export function addToBeBudgetedGroup(groups: CategoryGroupEntity[]) { return [ { id: 'to-be-budgeted', @@ -11,20 +14,20 @@ export function addToBeBudgetedGroup(groups) { ]; } -export function separateGroups(categoryGroups) { +export function separateGroups(categoryGroups: CategoryGroupEntity[]) { return [ categoryGroups.filter(g => !g.is_income), categoryGroups.find(g => g.is_income), ]; } -export function makeAmountGrey(value) { +export function makeAmountGrey(value: number | string) { return value === 0 || value === '0' || value === '' ? { color: theme.altMenuItemText } : null; } -export function makeAmountStyle(value) { +export function makeAmountStyle(value: number) { const greyed = makeAmountGrey(value); if (greyed) { return greyed; @@ -35,7 +38,7 @@ export function makeAmountStyle(value) { } } -export function makeAmountFullStyle(value) { +export function makeAmountFullStyle(value: number) { return { color: value < 0 @@ -46,7 +49,11 @@ export function makeAmountFullStyle(value) { }; } -export function findSortDown(arr, pos, targetId) { +export function findSortDown( + arr: CategoryGroupEntity[], + pos: DropPosition, + targetId: string, +) { if (pos === 'top') { return { targetId }; } else { @@ -66,7 +73,11 @@ export function findSortDown(arr, pos, targetId) { } } -export function findSortUp(arr, pos, targetId) { +export function findSortUp( + arr: CategoryGroupEntity[], + pos: DropPosition, + targetId: string, +) { if (pos === 'bottom') { return { targetId }; } else { diff --git a/packages/desktop-client/src/components/common/ButtonLink.tsx b/packages/desktop-client/src/components/common/ButtonLink.tsx index dca78f6c2d4..1ab6215b61d 100644 --- a/packages/desktop-client/src/components/common/ButtonLink.tsx +++ b/packages/desktop-client/src/components/common/ButtonLink.tsx @@ -1,6 +1,7 @@ import React, { type ComponentProps } from 'react'; -import { useMatch, useNavigate } from 'react-router-dom'; +import { useMatch } from 'react-router-dom'; +import useNavigate from '../../hooks/useNavigate'; import { type CSSProperties } from '../../style'; import Button from './Button'; diff --git a/packages/desktop-client/src/components/common/Link.tsx b/packages/desktop-client/src/components/common/Link.tsx index 19f7fc08ec0..17b146cf349 100644 --- a/packages/desktop-client/src/components/common/Link.tsx +++ b/packages/desktop-client/src/components/common/Link.tsx @@ -1,8 +1,9 @@ import React, { type ReactNode, type ComponentProps } from 'react'; -import { NavLink, useMatch, useNavigate } from 'react-router-dom'; +import { NavLink, useMatch } from 'react-router-dom'; import { css } from 'glamor'; +import useNavigate from '../../hooks/useNavigate'; import { type CSSProperties, styles } from '../../style'; import Button from './Button'; diff --git a/packages/desktop-client/src/components/common/Modal.tsx b/packages/desktop-client/src/components/common/Modal.tsx index 2bab19bc7f5..a03bdd004b3 100644 --- a/packages/desktop-client/src/components/common/Modal.tsx +++ b/packages/desktop-client/src/components/common/Modal.tsx @@ -18,7 +18,7 @@ import Text from './Text'; import View from './View'; export type ModalProps = { - title: string; + title?: string; isCurrent?: boolean; isHidden?: boolean; children: ReactNode | (() => ReactNode); diff --git a/packages/desktop-client/src/components/manager/ConfigServer.tsx b/packages/desktop-client/src/components/manager/ConfigServer.tsx index 1331fa5676c..cda664eb51a 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; import { isNonProductionEnvironment, @@ -7,6 +6,7 @@ import { } from 'loot-core/src/shared/environment'; import { useActions } from '../../hooks/useActions'; +import useNavigate from '../../hooks/useNavigate'; import { useSetThemeColor } from '../../hooks/useSetThemeColor'; import { theme } from '../../style'; import Button, { ButtonWithLoading } from '../common/Button'; diff --git a/packages/desktop-client/src/components/manager/WelcomeScreen.js b/packages/desktop-client/src/components/manager/WelcomeScreen.js index 53125f8d2f2..4d34b235d67 100644 --- a/packages/desktop-client/src/components/manager/WelcomeScreen.js +++ b/packages/desktop-client/src/components/manager/WelcomeScreen.js @@ -48,7 +48,7 @@ export default function WelcomeScreen() { to help you get your bearings, and check out the rest of the documentation while you’re there to learn more about advanced topics. - + Get started by importing an existing budget file from Actual or another budgeting app, or start fresh with an empty budget. You can always create or import another budget later. diff --git a/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx b/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx index 5ee9f149963..68adb49b8d4 100644 --- a/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx @@ -1,8 +1,8 @@ import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; import { send } from 'loot-core/src/platform/client/fetch'; +import useNavigate from '../../../hooks/useNavigate'; import { theme } from '../../../style'; import Button from '../../common/Button'; import Text from '../../common/Text'; diff --git a/packages/desktop-client/src/components/manager/subscribe/Error.tsx b/packages/desktop-client/src/components/manager/subscribe/Error.tsx index 969a0856358..30429f89fd2 100644 --- a/packages/desktop-client/src/components/manager/subscribe/Error.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/Error.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; +import useNavigate from '../../../hooks/useNavigate'; import { theme } from '../../../style'; import Button from '../../common/Button'; import Text from '../../common/Text'; diff --git a/packages/desktop-client/src/components/manager/subscribe/common.tsx b/packages/desktop-client/src/components/manager/subscribe/common.tsx index fdc940f1ba4..9cc46ca8a66 100644 --- a/packages/desktop-client/src/components/manager/subscribe/common.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/common.tsx @@ -1,8 +1,9 @@ import React, { useEffect, useState } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { send } from 'loot-core/src/platform/client/fetch'; +import useNavigate from '../../../hooks/useNavigate'; import { theme } from '../../../style'; import { useSetServerURL } from '../../ServerContext'; diff --git a/packages/desktop-client/src/components/mobile/MobileNavTabs.tsx b/packages/desktop-client/src/components/mobile/MobileNavTabs.tsx new file mode 100644 index 00000000000..afa3c89b73a --- /dev/null +++ b/packages/desktop-client/src/components/mobile/MobileNavTabs.tsx @@ -0,0 +1,82 @@ +import React, { type ComponentType, useMemo } from 'react'; +import { NavLink } from 'react-router-dom'; + +import usePrevious from '../../hooks/usePrevious'; +import Add from '../../icons/v1/Add'; +import Cog from '../../icons/v1/Cog'; +import PiggyBank from '../../icons/v1/PiggyBank'; +import Wallet from '../../icons/v1/Wallet'; +import { useResponsive } from '../../ResponsiveProvider'; +import { theme, styles } from '../../style'; +import { useScroll } from '../ScrollProvider'; + +const height = 70; + +export default function MobileNavTabs() { + const { isNarrowWidth } = useResponsive(); + const { scrollY, isBottomReached } = useScroll(); + const previousScrollY = usePrevious(scrollY); + + const isVisible = useMemo( + () => + previousScrollY === undefined || + (!isBottomReached && previousScrollY > scrollY) || + previousScrollY < 0, + [scrollY], + ); + + return ( +
+ + + + +
+ ); +} + +type NavTabIconProps = { + width: number; + height: number; +}; + +type NavTabProps = { + name: string; + path: string; + icon: ComponentType; +}; + +function NavTab({ icon: TabIcon, name, path }: NavTabProps) { + return ( + ({ + ...styles.noTapHighlight, + alignItems: 'center', + color: isActive ? theme.mobileNavItemSelected : theme.mobileNavItem, + display: 'flex', + flexDirection: 'column', + textDecoration: 'none', + })} + > + + {name} + + ); +} diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccount.tsx b/packages/desktop-client/src/components/modals/CreateLocalAccount.tsx index 349cc4a221d..44984c491de 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccount.tsx +++ b/packages/desktop-client/src/components/modals/CreateLocalAccount.tsx @@ -1,9 +1,9 @@ import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; import { toRelaxedNumber } from 'loot-core/src/shared/util'; import { type BoundActions } from '../../hooks/useActions'; +import useNavigate from '../../hooks/useNavigate'; import { theme } from '../../style'; import { type CommonModalProps } from '../../types/modals'; import Button from '../common/Button'; @@ -110,7 +110,7 @@ function CreateLocalAccount({ modalProps, actions }: CreateLocalAccountProps) { style={{ textAlign: 'right', fontSize: '0.7em', - color: theme.altpageTextSubdued, + color: theme.pageTextLight, marginTop: 3, }} > diff --git a/packages/desktop-client/src/components/modals/EditRule.js b/packages/desktop-client/src/components/modals/EditRule.js index 603bc30cb8b..296ece82af3 100644 --- a/packages/desktop-client/src/components/modals/EditRule.js +++ b/packages/desktop-client/src/components/modals/EditRule.js @@ -283,7 +283,7 @@ function ScheduleDescription({ id }) {
diff --git a/packages/desktop-client/src/components/modals/FixEncryptionKey.js b/packages/desktop-client/src/components/modals/FixEncryptionKey.tsx similarity index 92% rename from packages/desktop-client/src/components/modals/FixEncryptionKey.js rename to packages/desktop-client/src/components/modals/FixEncryptionKey.tsx index 57e52e37edf..d327898f928 100644 --- a/packages/desktop-client/src/components/modals/FixEncryptionKey.js +++ b/packages/desktop-client/src/components/modals/FixEncryptionKey.tsx @@ -1,9 +1,12 @@ import React, { useState } from 'react'; +import { type FinanceModals } from 'loot-core/src/client/state-types/modals'; import { send } from 'loot-core/src/platform/client/fetch'; import { getTestKeyError } from 'loot-core/src/shared/errors'; +import { type BoundActions } from '../../hooks/useActions'; import { theme } from '../../style'; +import { type CommonModalProps } from '../../types/modals'; import Button, { ButtonWithLoading } from '../common/Button'; import ExternalLink from '../common/ExternalLink'; import InitialFocus from '../common/InitialFocus'; @@ -13,11 +16,17 @@ import Paragraph from '../common/Paragraph'; import Text from '../common/Text'; import View from '../common/View'; +type FixEncryptionKeyProps = { + modalProps: CommonModalProps; + actions: BoundActions; + options: FinanceModals['fix-encryption-key']; +}; + export default function FixEncryptionKey({ modalProps, actions, options = {}, -}) { +}: FixEncryptionKeyProps) { let { hasExistingKey, cloudFileId, onSuccess } = options; let [password, setPassword] = useState(''); diff --git a/packages/desktop-client/src/components/modals/PlaidExternalMsg.js b/packages/desktop-client/src/components/modals/PlaidExternalMsg.tsx similarity index 93% rename from packages/desktop-client/src/components/modals/PlaidExternalMsg.js rename to packages/desktop-client/src/components/modals/PlaidExternalMsg.tsx index a07ebc1abd7..1130d66d47a 100644 --- a/packages/desktop-client/src/components/modals/PlaidExternalMsg.js +++ b/packages/desktop-client/src/components/modals/PlaidExternalMsg.tsx @@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react'; import AnimatedLoading from '../../icons/AnimatedLoading'; import { theme } from '../../style'; +import { type CommonModalProps } from '../../types/modals'; import { Error } from '../alerts'; import Button from '../common/Button'; import Modal, { ModalButtons } from '../common/Modal'; @@ -19,12 +20,19 @@ function renderError(error) { ); } +type PlainExternalMsgProps = { + modalProps: CommonModalProps; + onMoveExternal: () => Promise<{ error; data }>; + onSuccess: (data: unknown) => Promise; + onClose?: () => void; +}; + export default function PlaidExternalMsg({ modalProps, onMoveExternal, onSuccess, onClose: originalOnClose, -}) { +}: PlainExternalMsgProps) { let [waiting, setWaiting] = useState(null); let [success, setSuccess] = useState(false); let [error, setError] = useState(null); diff --git a/packages/desktop-client/src/components/payees/ManagePayeesPage.js b/packages/desktop-client/src/components/payees/ManagePayeesPage.js index f4d5e903187..2d707f39131 100644 --- a/packages/desktop-client/src/components/payees/ManagePayeesPage.js +++ b/packages/desktop-client/src/components/payees/ManagePayeesPage.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useLocation } from 'react-router'; +import { useLocation } from 'react-router-dom'; import { Page } from '../Page'; diff --git a/packages/desktop-client/src/components/reports/graphs/CashFlowGraph.tsx b/packages/desktop-client/src/components/reports/graphs/CashFlowGraph.tsx index 51263a18a8e..f5739966f4e 100644 --- a/packages/desktop-client/src/components/reports/graphs/CashFlowGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/CashFlowGraph.tsx @@ -35,7 +35,10 @@ function CashFlowGraph({ graphData, isConcise }: CashFlowGraphProps) { } > - + } labels={x => x.premadeLabel} style={{ - data: { stroke: theme.altpageTextSubdued }, + data: { stroke: theme.pageTextLight }, }} /> @@ -634,7 +634,7 @@ export default function ScheduleDetails({ modalProps, actions, id }) { {state.isCustom && ( Promise | void; type UseDraggableArgs = { item: unknown; @@ -64,7 +66,6 @@ export function useDraggable({ return { dragRef }; } -type DropPosition = 'top' | 'bottom'; export type OnDropCallback = ( id: unknown, @@ -129,7 +130,7 @@ export const DropHighlightPosContext: Context = createContext(null); type DropHighlightProps = { - pos: 'top' | 'bottom'; + pos: DropPosition; offset?: { top?: number; bottom?: number; diff --git a/packages/desktop-client/src/components/transactions/MobileTransaction.js b/packages/desktop-client/src/components/transactions/MobileTransaction.js index 28f7fecce06..51bf4e58dfd 100644 --- a/packages/desktop-client/src/components/transactions/MobileTransaction.js +++ b/packages/desktop-client/src/components/transactions/MobileTransaction.js @@ -7,7 +7,7 @@ import React, { useRef, } from 'react'; import { useSelector } from 'react-redux'; -import { useNavigate, useParams, Link } from 'react-router-dom'; +import { useParams, Link } from 'react-router-dom'; import { useFocusRing } from '@react-aria/focus'; import { useListBox, useListBoxSection, useOption } from '@react-aria/listbox'; @@ -45,6 +45,7 @@ import { import { useActions } from '../../hooks/useActions'; import useCategories from '../../hooks/useCategories'; +import useNavigate from '../../hooks/useNavigate'; import { useSetThemeColor } from '../../hooks/useSetThemeColor'; import SvgAdd from '../../icons/v1/Add'; import CheveronLeft from '../../icons/v1/CheveronLeft'; @@ -157,7 +158,7 @@ function Status({ status }) { switch (status) { case 'missed': - color = theme.alt3ErrorText; + color = theme.errorText; break; case 'due': color = theme.alt2WarningText; @@ -986,7 +987,7 @@ class Transaction extends PureComponent { let isReconciled = transaction.reconciled; let textStyle = isPreview && { fontStyle: 'italic', - color: theme.altpageTextSubdued, + color: theme.pageTextLight, }; return ( diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.js b/packages/desktop-client/src/components/transactions/TransactionsTable.js index bc0a9f24e46..70208ceb7a8 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.js +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.js @@ -421,11 +421,11 @@ function StatusCell({ : status === 'reconciled' ? theme.noticeTextLight : status === 'missed' - ? theme.alt5ErrorText + ? theme.errorText : status === 'due' ? theme.alt3WarningText : selected - ? theme.altPageTextLink + ? theme.pageTextLinkLight : theme.pageTextSubdued; function onSelect() { @@ -1076,7 +1076,7 @@ const Transaction = memo(function Transaction(props) { style={{ color: notes === 'missed' - ? theme.alt5ErrorText + ? theme.errorText : notes === 'due' ? theme.alt5WarningText : selected @@ -1084,7 +1084,7 @@ const Transaction = memo(function Transaction(props) { : theme.altTableText, backgroundColor: notes === 'missed' - ? theme.altErrorBackground + ? theme.errorBackground : notes === 'due' ? theme.altWarningBackground : selected diff --git a/packages/desktop-client/src/components/util/DisplayId.js b/packages/desktop-client/src/components/util/DisplayId.tsx similarity index 89% rename from packages/desktop-client/src/components/util/DisplayId.js rename to packages/desktop-client/src/components/util/DisplayId.tsx index 3947f9f1e5d..0a66bf34f2f 100644 --- a/packages/desktop-client/src/components/util/DisplayId.js +++ b/packages/desktop-client/src/components/util/DisplayId.tsx @@ -6,11 +6,17 @@ import { CachedPayees } from 'loot-core/src/client/data-hooks/payees'; import { theme } from '../../style'; import Text from '../common/Text'; +type DisplayIdProps = { + type: 'accounts' | 'payees'; + id: string; + noneColor?: string; +}; + export default function DisplayId({ type, id, noneColor = theme.pageTextSubdued, -}) { +}: DisplayIdProps) { let DataComponent; switch (type) { diff --git a/packages/desktop-client/src/hooks/useNavigate.ts b/packages/desktop-client/src/hooks/useNavigate.ts new file mode 100644 index 00000000000..21387e423b0 --- /dev/null +++ b/packages/desktop-client/src/hooks/useNavigate.ts @@ -0,0 +1,51 @@ +import { useCallback } from 'react'; +import { + type Location, + type NavigateFunction, + type NavigateOptions, + type To, + useLocation, + useNavigate as useNavigateReactRouter, +} from 'react-router-dom'; + +export default function useNavigate(): NavigateFunction { + const location = useLocation(); + const navigate = useNavigateReactRouter(); + return useCallback( + (to: To | number, options: NavigateOptions = {}) => { + if (typeof to === 'number') { + navigate(to); + } else { + const optionsWithPrevLocation: NavigateOptions = { + replace: + options.replace || isSamePath(to, location) ? true : undefined, + ...options, + state: { + ...options?.state, + previousLocation: location, + }, + }; + + let { previousLocation, ...previousOriginalState } = + location.state || {}; + + if ( + previousLocation == null || + !isSamePath(to, previousLocation) || + JSON.stringify(options?.state || {}) !== + JSON.stringify(previousOriginalState) + ) { + navigate(to, optionsWithPrevLocation); + } else { + // `to` is the same as the previous location. Just go back. + navigate(-1); + } + } + }, + [navigate, location], + ); +} + +function isSamePath(to: To, location: Location) { + return to === location.pathname + location.search + location.hash; +} diff --git a/packages/desktop-client/src/icons/AnimatedLoading.js b/packages/desktop-client/src/icons/AnimatedLoading.tsx similarity index 73% rename from packages/desktop-client/src/icons/AnimatedLoading.js rename to packages/desktop-client/src/icons/AnimatedLoading.tsx index 803c86f1c6f..a153265a405 100644 --- a/packages/desktop-client/src/icons/AnimatedLoading.js +++ b/packages/desktop-client/src/icons/AnimatedLoading.tsx @@ -1,15 +1,15 @@ -import React from 'react'; +import React, { type SVGProps } from 'react'; -import { css } from 'glamor'; +import { css, keyframes } from 'glamor'; import Loading from './Loading'; -const rotation = css.keyframes({ +const rotation = keyframes({ '0%': { transform: 'rotate(-90deg)' }, '100%': { transform: 'rotate(666deg)' }, }); -function AnimatedLoading(props) { +function AnimatedLoading(props: SVGProps) { return ( { +const SvgLoading = (props: SVGProps) => { let { color = 'currentColor' } = props; let [gradientId] = useState('gradient-' + Math.random()); diff --git a/packages/desktop-client/src/icons/logo/Logo.js b/packages/desktop-client/src/icons/logo/Logo.tsx similarity index 89% rename from packages/desktop-client/src/icons/logo/Logo.js rename to packages/desktop-client/src/icons/logo/Logo.tsx index 32ab54a0676..60c4d53af4d 100644 --- a/packages/desktop-client/src/icons/logo/Logo.js +++ b/packages/desktop-client/src/icons/logo/Logo.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -const SvgLogo = props => ( +import type { SVGProps } from 'react'; +const SvgLogo = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAdd = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDelete = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgExpandArrow = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLeftArrow2 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMath = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMerge = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgRightArrow2 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSplit = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSubtract = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAdd = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAddOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAddSolid = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAdjust = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAirplane = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAlbum = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAlignCenter = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAlignJustified = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAlignLeft = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAlignRight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAnchor = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAnnouncement = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgApparel = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowDown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowLeft = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowOutlineDown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowOutlineLeft = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowOutlineRight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowOutlineUp = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowRight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowThickDown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowThickLeft = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowThickRight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowThickUp = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowThinDown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowThinLeft = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowThinRight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowThinUp = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowUp = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArtist = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAtSymbol = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAttachment = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBackspace = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBackward = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBackwardStep = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBadge = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBatteryFull = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBatteryHalf = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBatteryLow = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBeverage = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBlock = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBluetooth = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBolt = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBookReference = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBookmark = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBookmarkCopy2 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBookmarkCopy3 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBookmarkOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBookmarkOutlineAdd = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderAll = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderBottom = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderHorizontal = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderInner = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderLeft = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderNone = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderOuter = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderRight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderTop = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBorderVertical = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBox = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBrightnessDown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBrightnessUp = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBrowserWindow = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBrowserWindowNew = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBrowserWindowOpen = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBug = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgBuoy = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCalculator = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCalendar = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCamera = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgChart = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgChartBar = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgChartPie = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgChatBubbleDots = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheckAlternative = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheckmark = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheckmarkOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheveronDown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheveronLeft = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheveronOutlineDown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheveronOutlineLeft = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheveronOutlineRight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheveronOutlineUp = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheveronRight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheveronUp = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgClipboard = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgClose = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCloseOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCloseSolid = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCloud = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCloudCheck = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCloudDownload = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCloudUpload = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCloudWarning = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCode = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCoffee = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCog = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgColorPalette = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCompose = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgComputerDesktop = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgComputerLaptop = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgConversation = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCopy = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCreditCard = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCurrencyDollar = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDashboard = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDateAdd = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDialPad = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDirections = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDocument = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDocumentAdd = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDotsHorizontalDouble = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDotsHorizontalTriple = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDownload = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDuplicate = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgEditCopy = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgEditCrop = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgEditCut = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgEditPencil = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgEducation = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgEnvelope = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgEquals = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgExclamationOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgExclamationSolid = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgExplore = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFactory = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFastForward = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFastRewind = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFileDouble = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFilm = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFilter = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFlag = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFlashlight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFolder = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFolderOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFolderOutlineAdd = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFormatBold = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFormatFontSize = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFormatItalic = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFormatTextSize = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFormatUnderline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgForward = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgForwardStep = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgGift = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgGlobe = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgHandStop = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgHardDrive = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgHeadphones = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgHeart = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgHome = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgHot = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgHourGlass = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgInbox = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgInboxCheck = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgInboxDownload = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgInboxFull = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgIndentDecrease = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgIndentIncrease = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgInformationOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgInformationSolid = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgKey = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgKeyboard = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLayers = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLibrary = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLightBulb = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLink = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgList = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgListAdd = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgListBullet = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLoadBalancer = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLocation = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLocationCurrent = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLocationFood = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLocationGasStation = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLocationHotel = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLocationMarina = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLocationPark = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLocationRestroom = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLocationShopping = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLockClosed = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgLockOpen = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMap = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMenu = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMic = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMinusOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMinusSolid = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMobileDevices = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMoneyBag = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMoodHappyOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMoodHappySolid = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMoodNeutralOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMoodNeutralSolid = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMoodSadOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMoodSadSolid = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMouse = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMoveBack = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMusicAlbum = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMusicArtist = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMusicNotes = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMusicPlaylist = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgNavigationMore = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgNetwork = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgNewsPaper = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgNotification = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgNotifications = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgNotificationsOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPaste = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPause = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPauseOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPauseSolid = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPenTool = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPencilWrite = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPhone = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPhoto = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPhpElephant = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPiggyBank = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPin = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPlay = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPlayOutline = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPlaylist = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPlugin = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPortfolio = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPrinter = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPylon = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgQuestion = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgQueue = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgRadar = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgRadarCopy2 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgRadio = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgRefresh = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgReload = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgReply = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgReplyAll = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgReports = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgRepost = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSaveDisk = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgScreenFull = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSearch = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSend = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgServers = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgShare = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgShare01 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgShareAlt = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgShield = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgShoppingCart = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgShowSidebar = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgShuffle = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgStandBy = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgStarFull = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgStation = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgStepBackward = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgStepForward = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgStethoscope = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgStoreFront = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgStrokeWidth = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSubdirectoryLeft = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSubdirectoryRight = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSubtract = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSwap = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTablet = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTag = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTarget = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTextBox = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTextDecoration = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgThermometer = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgThumbsDown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgThumbsUp = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTicket = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTime = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTimer = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgToolsCopy = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTranslate = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTrash = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTravel = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTravelBus = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTravelCar = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTravelCase = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTravelTaxiCab = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTravelTrain = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTravelWalk = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTrophy = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgTuning = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgUpload = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgUsb = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgUser = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgUserAdd = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgUserGroup = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgUserSolidCircle = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgUserSolidSquare = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgVector = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgVideoCamera = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgViewCarousel = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgViewColumn = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgViewHide = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgViewList = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgViewShow = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgViewTile = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgVolumeDown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgVolumeMute = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgVolumeOff = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgVolumeUp = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgWallet = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgWatch = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgWindow = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgWindowNew = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgWindowOpen = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgWrench = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgYinYang = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgZoomIn = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgZoomOut = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgAlertTriangle = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowButtonDown1 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowButtonLeft1 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowButtonRight1 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowButtonUp1 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowsExpand3 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowsShrink3 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgArrowsSynchronize = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCalendar = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCalendar3 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheck = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheckCircle1 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCheckCircleHollow = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCloudUnknown = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCloudUpload = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgCustomNotesPaper = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgDownloadThickBottom = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgEditSkull1 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgEye = (props: SVGProps) => ( ( strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round', - stroke: 'currentColor', + stroke: '#000', strokeOpacity: 1, strokeMiterlimit: 4, }} transform="matrix(.96 0 0 .96 1 .48)" + fill="currentColor" /> ( strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round', - stroke: 'currentColor', + stroke: '#000', strokeOpacity: 1, strokeMiterlimit: 4, }} transform="matrix(.96 0 0 .96 1 .48)" + fill="currentColor" /> ( +import type { SVGProps } from 'react'; +const SvgEyeSlashed = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFavoriteStar = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgFilter2 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgHyperlink2 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgHyperlink3 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgInformationCircle = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgKey = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgMoonStars = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgNavigationMenu = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgNotesPaper = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgNotesPaperText = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPencil1 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgPencilWriteAlternate = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgRefreshArrow = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgRemove = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgRemoveAlternate = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSearch1 = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSearchAlternate = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSettingsSliderAlternate = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSubtract = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgSun = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgUploadThickBottom = (props: SVGProps) => ( ( +import type { SVGProps } from 'react'; +const SvgValidationCheck = (props: SVGProps) => ( >; - payees: unknown[]; + payees: Awaited>; earliestTransaction: unknown | null; }; @@ -51,7 +51,7 @@ type LoadCategoriesAction = { type LoadPayeesAction = { type: typeof constants.LOAD_PAYEES; - payees: unknown[]; + payees: State['payees']; }; export type QueriesActions = diff --git a/packages/loot-core/src/server/rules/types/handlers.ts b/packages/loot-core/src/server/rules/types/handlers.ts index 27e09e3e821..721172c4848 100644 --- a/packages/loot-core/src/server/rules/types/handlers.ts +++ b/packages/loot-core/src/server/rules/types/handlers.ts @@ -23,7 +23,7 @@ export interface RulesHandlers { rule: Partial, ) => Promise<{ error: ValidationError } | object>; - 'rule-delete': (rule: RuleEntity) => Promise; + 'rule-delete': (rule: Required) => Promise; 'rule-delete-all': ( ids: string[], diff --git a/packages/loot-core/src/shared/rules.ts b/packages/loot-core/src/shared/rules.ts index d0fa730a44a..3857aff31e2 100644 --- a/packages/loot-core/src/shared/rules.ts +++ b/packages/loot-core/src/shared/rules.ts @@ -45,7 +45,7 @@ export const FIELD_TYPES = new Map( }), ); -export function mapField(field, opts) { +export function mapField(field, opts?) { opts = opts || {}; switch (field) { diff --git a/packages/loot-core/src/types/models/rule.d.ts b/packages/loot-core/src/types/models/rule.d.ts index a374ed29bcf..fee3e047824 100644 --- a/packages/loot-core/src/types/models/rule.d.ts +++ b/packages/loot-core/src/types/models/rule.d.ts @@ -1,7 +1,7 @@ import { type ScheduleEntity } from './schedule'; export interface RuleEntity { - id: string; + id?: string; stage: string; conditionsOp: 'any' | 'and'; conditions: RuleConditionEntity[]; @@ -15,6 +15,7 @@ interface RuleConditionEntity { value: unknown; options?: unknown; conditionsOp?: unknown; + type?: string; } export type RuleActionEntity = @@ -26,6 +27,7 @@ export interface SetRuleActionEntity { op: 'set'; value: unknown; options?: unknown; + type?: string; } export interface LinkScheduleRuleActionEntity { diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts index e7c3e7ba0ca..82660722163 100644 --- a/packages/loot-core/src/types/server-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -5,7 +5,7 @@ import { Backup } from '../server/backups'; import { RemoteFile } from '../server/cloud-storage'; import { Message } from '../server/sync'; -import { AccountEntity } from './models'; +import { AccountEntity, PayeeEntity } from './models'; import { EmptyObject } from './util'; export interface ServerHandlers { @@ -95,7 +95,7 @@ export interface ServerHandlers { 'payee-create': (arg: { name }) => Promise; - 'payees-get': () => Promise; + 'payees-get': () => Promise; 'payees-get-rule-counts': () => Promise; diff --git a/upcoming-release-notes/1745.md b/upcoming-release-notes/1745.md new file mode 100644 index 00000000000..356a8157cd1 --- /dev/null +++ b/upcoming-release-notes/1745.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [joel-jeremy,MatissJanis] +--- + +Hide mobile nav bar when scrolling diff --git a/upcoming-release-notes/1756.md b/upcoming-release-notes/1756.md new file mode 100644 index 00000000000..8f0807b2308 --- /dev/null +++ b/upcoming-release-notes/1756.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [carkom] +--- + +Conslidating and making consistent error colors across all pages in the app. diff --git a/upcoming-release-notes/1784.md b/upcoming-release-notes/1784.md new file mode 100644 index 00000000000..530c9a07ec3 --- /dev/null +++ b/upcoming-release-notes/1784.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Convert FixEncryptionKey, Loading, AnimatedLoading components to TypeScript and update get-payee query type. diff --git a/upcoming-release-notes/1785.md b/upcoming-release-notes/1785.md new file mode 100644 index 00000000000..52406089dfb --- /dev/null +++ b/upcoming-release-notes/1785.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Update generated icons to typescript diff --git a/upcoming-release-notes/1799.md b/upcoming-release-notes/1799.md new file mode 100644 index 00000000000..a66d715b823 --- /dev/null +++ b/upcoming-release-notes/1799.md @@ -0,0 +1,5 @@ +--- +category: Enhancements +authors: [carkom] +--- +Consolidating and making consistent page colors diff --git a/upcoming-release-notes/1808.md b/upcoming-release-notes/1808.md new file mode 100644 index 00000000000..dd21148d476 --- /dev/null +++ b/upcoming-release-notes/1808.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [joel-jeremy] +--- + +Fix flaky mobile back button on account transactions. diff --git a/upcoming-release-notes/1814.md b/upcoming-release-notes/1814.md new file mode 100644 index 00000000000..f04b5b376d9 --- /dev/null +++ b/upcoming-release-notes/1814.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [shaankhosla] +--- + +Added 2 new VRT tests for reports. diff --git a/upcoming-release-notes/1819.md b/upcoming-release-notes/1819.md new file mode 100644 index 00000000000..4f0c8ee8fb6 --- /dev/null +++ b/upcoming-release-notes/1819.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [shaankhosla] +--- + +Fix styling on cash-flow graph. diff --git a/upcoming-release-notes/1823.md b/upcoming-release-notes/1823.md new file mode 100644 index 00000000000..4df7bd57b66 --- /dev/null +++ b/upcoming-release-notes/1823.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Convert Sort Utils, DisplayId, PlaidExternalMsg components to Typescript