diff --git a/packages/desktop-client/src/components/UpdateNotification.tsx b/packages/desktop-client/src/components/UpdateNotification.tsx index 1485a56a96b..58b769d4596 100644 --- a/packages/desktop-client/src/components/UpdateNotification.tsx +++ b/packages/desktop-client/src/components/UpdateNotification.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React from 'react'; import { useSelector } from 'react-redux'; @@ -11,14 +10,6 @@ import { LinkButton } from './common/LinkButton'; import { Text } from './common/Text'; import { View } from './common/View'; -function closeNotification(setAppState) { - // Set a flag to never show an update notification again for this session - setAppState({ - updateInfo: null, - showUpdateNotification: false, - }); -} - export function UpdateNotification() { const updateInfo = useSelector(state => state.app.updateInfo); const showUpdateNotification = useSelector( @@ -68,7 +59,7 @@ export function UpdateNotification() { textDecoration: 'underline', }} onClick={() => - window.Actual.openURLInBrowser( + window.Actual?.openURLInBrowser( 'https://actualbudget.org/docs/releases', ) } @@ -80,7 +71,13 @@ export function UpdateNotification() { type="bare" aria-label="Close" style={{ display: 'inline', padding: '1px 7px 2px 7px' }} - onClick={() => closeNotification(setAppState)} + onClick={() => { + // Set a flag to never show an update notification again for this session + setAppState({ + updateInfo: null, + showUpdateNotification: false, + }); + }} > ({ type AutocompleteFooterProps = { show?: boolean; - embedded: boolean; + embedded?: boolean; children: ReactNode; }; export function AutocompleteFooter({ @@ -699,18 +699,20 @@ export function AutocompleteFooter({ embedded, children, }: AutocompleteFooterProps) { + if (!show) { + return null; + } + return ( - show && ( - e.preventDefault()} - > - {children} - - ) + e.preventDefault()} + > + {children} + ); } diff --git a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx index 2fca26a7a71..bb34a8eeaa4 100644 --- a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React, { type ComponentProps, Fragment, @@ -25,9 +24,11 @@ import { Autocomplete, defaultFilterSuggestion } from './Autocomplete'; export type CategoryListProps = { items: Array; - getItemProps?: (arg: { item }) => Partial>; + getItemProps?: (arg: { + item: CategoryEntity; + }) => Partial>; highlightedIndex: number; - embedded: boolean; + embedded?: boolean; footer?: ReactNode; renderSplitTransactionButton?: ( props: SplitTransactionButtonProps, @@ -47,7 +48,7 @@ function CategoryList({ renderCategoryItemGroupHeader = defaultRenderCategoryItemGroupHeader, renderCategoryItem = defaultRenderCategoryItem, }: CategoryListProps) { - let lastGroup = null; + let lastGroup: string | undefined | null = null; return ( @@ -72,10 +73,10 @@ function CategoryList({ lastGroup = item.cat_group; return ( - {showGroup && ( - + {showGroup && item.group?.name && ( + {renderCategoryItemGroupHeader({ - title: item.group?.name, + title: item.group.name, })} )} @@ -125,7 +126,7 @@ export function CategoryAutocomplete({ categoryGroups.reduce( (list, group) => list.concat( - group.categories + (group.categories || []) .filter(category => category.cat_group === group.id) .map(category => ({ ...category, @@ -214,8 +215,7 @@ type SplitTransactionButtonProps = { style?: CSSProperties; }; -// eslint-disable-next-line import/no-unused-modules -export function SplitTransactionButton({ +function SplitTransactionButton({ Icon, highlighted, embedded, diff --git a/packages/loot-core/src/platform/server/connection/index.electron.ts b/packages/loot-core/src/platform/server/connection/index.electron.ts index 75e6a273106..5e48c200d37 100644 --- a/packages/loot-core/src/platform/server/connection/index.electron.ts +++ b/packages/loot-core/src/platform/server/connection/index.electron.ts @@ -1,4 +1,5 @@ // @ts-strict-ignore +import { APIError } from '../../../server/errors'; import { runHandler, isMutating } from '../../../server/mutators'; import { captureException } from '../../exceptions'; @@ -70,7 +71,7 @@ export const init: T.Init = function (_socketName, handlers) { type: 'reply', id, result: null, - error: { type: 'APIError', message: 'Unknown method: ' + name }, + error: APIError('Unknown method: ' + name), }); } }); diff --git a/packages/loot-core/src/platform/server/connection/index.web.ts b/packages/loot-core/src/platform/server/connection/index.web.ts index 212f64e96eb..465b8348edc 100644 --- a/packages/loot-core/src/platform/server/connection/index.web.ts +++ b/packages/loot-core/src/platform/server/connection/index.web.ts @@ -1,4 +1,5 @@ // @ts-strict-ignore +import { APIError } from '../../../server/errors'; import { runHandler, isMutating } from '../../../server/mutators'; import { captureException } from '../../exceptions'; @@ -90,7 +91,7 @@ export const init: T.Init = function (serverChn, handlers) { type: 'reply', id, result: null, - error: { type: 'APIError', message: 'Unknown method: ' + name }, + error: APIError('Unknown method: ' + name), }); } }, diff --git a/packages/loot-core/src/server/api.ts b/packages/loot-core/src/server/api.ts index a89625971e9..2956ce61aef 100644 --- a/packages/loot-core/src/server/api.ts +++ b/packages/loot-core/src/server/api.ts @@ -28,6 +28,7 @@ import { import { runQuery as aqlQuery } from './aql'; import * as cloudStorage from './cloud-storage'; import * as db from './db'; +import { APIError } from './errors'; import { runMutator } from './mutators'; import * as prefs from './prefs'; import * as sheet from './sheet'; @@ -35,11 +36,6 @@ import { setSyncingMode, batchMessages } from './sync'; let IMPORT_MODE = false; -// This is duplicate from main.js... -function APIError(msg, meta?) { - return { type: 'APIError', message: msg, meta }; -} - // The API is different in two ways: we never want undo enabled, and // we also need to notify the UI manually if stuff has changed (if // they are connecting to an already running instance, the UI should diff --git a/packages/loot-core/src/server/db/sort.ts b/packages/loot-core/src/server/db/sort.ts index 1a8b96378e1..eb59865317d 100644 --- a/packages/loot-core/src/server/db/sort.ts +++ b/packages/loot-core/src/server/db/sort.ts @@ -1,7 +1,6 @@ -// @ts-strict-ignore export const SORT_INCREMENT = 16384; -function midpoint(items, to) { +function midpoint(items: T[], to: number) { const below = items[to - 1]; const above = items[to]; @@ -14,11 +13,14 @@ function midpoint(items, to) { } } -export function shoveSortOrders(items, targetId?: string) { +export function shoveSortOrders( + items: T[], + targetId?: string, +) { const to = items.findIndex(item => item.id === targetId); const target = items[to]; const before = items[to - 1]; - const updates = []; + const updates: Array<{ id: string; sort_order: number }> = []; // If no target is specified, append at the end if (!targetId || to === -1) { diff --git a/packages/loot-core/src/server/errors.ts b/packages/loot-core/src/server/errors.ts index 96606b4be2f..1edf89a9b68 100644 --- a/packages/loot-core/src/server/errors.ts +++ b/packages/loot-core/src/server/errors.ts @@ -1,11 +1,10 @@ -// @ts-strict-ignore // TODO: normalize error types export class PostError extends Error { - meta; - reason; - type; + meta?: { meta: string }; + reason: string; + type: 'PostError'; - constructor(reason, meta?) { + constructor(reason: string, meta?: { meta: string }) { super('PostError: ' + reason); this.type = 'PostError'; this.reason = reason; @@ -14,10 +13,10 @@ export class PostError extends Error { } export class HTTPError extends Error { - statusCode; - responseBody; + statusCode: number; + responseBody: string; - constructor(code, body) { + constructor(code: number, body: string) { super(`HTTPError: unsuccessful status code (${code}): ${body}`); this.statusCode = code; this.responseBody = body; @@ -25,10 +24,27 @@ export class HTTPError extends Error { } export class SyncError extends Error { - meta; - reason; + meta?: + | { + isMissingKey: boolean; + } + | { + error: { message: string; stack: string }; + query: { sql: string; params: Array }; + }; + reason: string; - constructor(reason, meta?) { + constructor( + reason: string, + meta?: + | { + isMissingKey: boolean; + } + | { + error: { message: string; stack: string }; + query: { sql: string; params: Array }; + }, + ) { super('SyncError: ' + reason); this.reason = reason; this.meta = meta; @@ -46,14 +62,20 @@ export class RuleError extends Error { } } -export function APIError(msg, meta?) { - return { type: 'APIError', message: msg, meta }; +export function APIError(msg: string) { + return { type: 'APIError', message: msg }; } -export function FileDownloadError(reason, meta?) { +export function FileDownloadError( + reason: string, + meta?: { fileId?: string; isMissingKey?: boolean }, +) { return { type: 'FileDownloadError', reason, meta }; } -export function FileUploadError(reason, meta?) { +export function FileUploadError( + reason: string, + meta?: { isMissingKey: boolean }, +) { return { type: 'FileUploadError', reason, meta }; } diff --git a/packages/loot-core/src/server/models.ts b/packages/loot-core/src/server/models.ts index 3616d7cf92c..03ddbeab060 100644 --- a/packages/loot-core/src/server/models.ts +++ b/packages/loot-core/src/server/models.ts @@ -1,19 +1,30 @@ -// @ts-strict-ignore -export function requiredFields(name, row, fields, update) { +import { + AccountEntity, + CategoryEntity, + CategoryGroupEntity, + PayeeEntity, +} from '../types/models'; + +export function requiredFields( + name: string, + row: T, + fields: K[], + update?: boolean, +) { fields.forEach(field => { if (update) { if (row.hasOwnProperty(field) && row[field] == null) { - throw new Error(`${name} is missing field ${field}`); + throw new Error(`${name} is missing field ${String(field)}`); } } else { if (!row.hasOwnProperty(field) || row[field] == null) { - throw new Error(`${name} is missing field ${field}`); + throw new Error(`${name} is missing field ${String(field)}`); } } }); } -export function toDateRepr(str) { +export function toDateRepr(str: string) { if (typeof str !== 'string') { throw new Error('toDateRepr not passed a string: ' + str); } @@ -21,7 +32,7 @@ export function toDateRepr(str) { return parseInt(str.replace(/-/g, '')); } -export function fromDateRepr(number) { +export function fromDateRepr(number: number) { if (typeof number !== 'number') { throw new Error('fromDateRepr not passed a number: ' + number); } @@ -37,7 +48,7 @@ export function fromDateRepr(number) { } export const accountModel = { - validate(account, { update }: { update?: boolean } = {}) { + validate(account: AccountEntity, { update }: { update?: boolean } = {}) { requiredFields( 'account', account, @@ -50,7 +61,7 @@ export const accountModel = { }; export const categoryModel = { - validate(category, { update }: { update?: boolean } = {}) { + validate(category: CategoryEntity, { update }: { update?: boolean } = {}) { requiredFields( 'category', category, @@ -64,7 +75,10 @@ export const categoryModel = { }; export const categoryGroupModel = { - validate(categoryGroup, { update }: { update?: boolean } = {}) { + validate( + categoryGroup: CategoryGroupEntity, + { update }: { update?: boolean } = {}, + ) { requiredFields( 'categoryGroup', categoryGroup, @@ -78,78 +92,8 @@ export const categoryGroupModel = { }; export const payeeModel = { - validate(payee, { update }: { update?: boolean } = {}) { + validate(payee: PayeeEntity, { update }: { update?: boolean } = {}) { requiredFields('payee', payee, ['name'], update); return payee; }, }; - -export const transactionModel = { - validate(trans, { update }: { update?: boolean } = {}) { - requiredFields('transaction', trans, ['date', 'acct'], update); - - if ('date' in trans) { - // Make sure it's the right format, and also do a sanity check. - // Really old dates can mess up the system and can happen by - // accident - if ( - trans.date.match(/^\d{4}-\d{2}-\d{2}$/) == null || - trans.date < '2000-01-01' - ) { - throw new Error('Invalid transaction date: ' + trans.date); - } - } - - return trans; - }, - - toJS(row) { - // Check a non-important field that typically wouldn't be passed in - // manually, and use it as a smoke test to see if this is a - // fully-formed transaction or not. - if (!('location' in row)) { - throw new Error( - 'A full transaction is required to be passed to `toJS`. Instead got: ' + - JSON.stringify(row), - ); - } - - const trans = { ...row }; - trans.error = row.error ? JSON.parse(row.error) : null; - trans.isParent = row.isParent === 1 ? true : false; - trans.isChild = row.isChild === 1 ? true : false; - trans.starting_balance_flag = - row.starting_balance_flag === 1 ? true : false; - trans.cleared = row.cleared === 1 ? true : false; - trans.pending = row.pending === 1 ? true : false; - trans.date = trans.date && fromDateRepr(trans.date); - return trans; - }, - - fromJS(trans) { - const row = { ...trans }; - if ('error' in row) { - row.error = trans.error ? JSON.stringify(trans.error) : null; - } - if ('isParent' in row) { - row.isParent = trans.isParent ? 1 : 0; - } - if ('isChild' in row) { - row.isChild = trans.isChild ? 1 : 0; - } - if ('cleared' in row) { - row.cleared = trans.cleared ? 1 : 0; - } - if ('pending' in row) { - row.pending = trans.pending ? 1 : 0; - } - if ('starting_balance_flag' in row) { - row.starting_balance_flag = trans.starting_balance_flag ? 1 : 0; - } - if ('date' in row) { - row.date = toDateRepr(trans.date); - } - - return row; - }, -}; diff --git a/packages/loot-core/src/server/reports/app.ts b/packages/loot-core/src/server/reports/app.ts index 612bc23ba73..8ee59b9127e 100644 --- a/packages/loot-core/src/server/reports/app.ts +++ b/packages/loot-core/src/server/reports/app.ts @@ -15,7 +15,7 @@ import { ReportsHandlers } from './types/handlers'; const reportModel = { validate(report: CustomReportEntity, { update }: { update?: boolean } = {}) { - requiredFields('reports', report, ['conditions'], update); + requiredFields('reports', report, ['conditionsOp'], update); if (!update || 'conditionsOp' in report) { if (!['and', 'or'].includes(report.conditionsOp)) { diff --git a/packages/loot-core/src/server/sync/index.ts b/packages/loot-core/src/server/sync/index.ts index 0cd7386bb4b..81a4679df7c 100644 --- a/packages/loot-core/src/server/sync/index.ts +++ b/packages/loot-core/src/server/sync/index.ts @@ -142,8 +142,7 @@ async function fetchAll(table, ids) { message: error.message, stack: error.stack, }, - sql, - params: partIds, + query: { sql, params: partIds }, }); } } diff --git a/upcoming-release-notes/2247.md b/upcoming-release-notes/2247.md new file mode 100644 index 00000000000..5aea7a9d7b2 --- /dev/null +++ b/upcoming-release-notes/2247.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +TypeScript: making some files comply with strict TS.