Compare commits
87 Commits
v24.1.0
...
ts-LoadBac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e71dcccd8 | ||
|
|
de9a1880a7 | ||
|
|
43ebe9e0fd | ||
|
|
515bdf5a74 | ||
|
|
018714610a | ||
|
|
00ee165f8e | ||
|
|
68442ae9e6 | ||
|
|
b937bfae04 | ||
|
|
317e7f135e | ||
|
|
5adb083575 | ||
|
|
524bd4e9eb | ||
|
|
9dfd6ce34c | ||
|
|
5d28bc0e3b | ||
|
|
a4e97e0070 | ||
|
|
a6e38ad2ae | ||
|
|
bb0ae4ebc3 | ||
|
|
dd29e02c5c | ||
|
|
75186183eb | ||
|
|
83f13cbdc8 | ||
|
|
0045d9212e | ||
|
|
dd254c6c23 | ||
|
|
c66d6e00f5 | ||
|
|
ae3be13aa8 | ||
|
|
cd9d1fb674 | ||
|
|
edba670d3a | ||
|
|
fbb1a9647d | ||
|
|
06cf65497f | ||
|
|
62a0a0fedc | ||
|
|
106dce25dd | ||
|
|
ff11399180 | ||
|
|
363c9e4afb | ||
|
|
e745a4073b | ||
|
|
7d1a895b48 | ||
|
|
a8b42bcd50 | ||
|
|
f33bce41ea | ||
|
|
cdefe6133f | ||
|
|
44a5199a31 | ||
|
|
dccad902d6 | ||
|
|
b477f7c2f1 | ||
|
|
540c410957 | ||
|
|
e205344867 | ||
|
|
8b968579b1 | ||
|
|
6ce794ffcc | ||
|
|
d5359a96ca | ||
|
|
3eee0b11d2 | ||
|
|
e792afb1fd | ||
|
|
4fb55d0d70 | ||
|
|
b330991855 | ||
|
|
165ad45822 | ||
|
|
295917036b | ||
|
|
ef9a7cfe85 | ||
|
|
4ece4a7ff6 | ||
|
|
761b3c6a16 | ||
|
|
7ace8c52dd | ||
|
|
f6dd0ecdb9 | ||
|
|
97a4296d7c | ||
|
|
89698480a5 | ||
|
|
1a6db82cfb | ||
|
|
6ce502ea24 | ||
|
|
fd962a97b0 | ||
|
|
300ed824f0 | ||
|
|
caca2497ea | ||
|
|
33e778fe9f | ||
|
|
8c43c78fc7 | ||
|
|
e0d82fd4f9 | ||
|
|
b6462347a9 | ||
|
|
8bf0f8e5bf | ||
|
|
bc07235017 | ||
|
|
794476ac51 | ||
|
|
30bc216142 | ||
|
|
6f8d2574ab | ||
|
|
9262b46428 | ||
|
|
8e1c11e18e | ||
|
|
6b5ee3f774 | ||
|
|
02d3f96c20 | ||
|
|
829c83afb2 | ||
|
|
5d29585fb7 | ||
|
|
882fd9f5cd | ||
|
|
d203def230 | ||
|
|
4d7cfab8bc | ||
|
|
84a9269ae4 | ||
|
|
83d2472a55 | ||
|
|
458d556e51 | ||
|
|
d9aeb8db1e | ||
|
|
85c0352abb | ||
|
|
7a40a645e6 | ||
|
|
7e9921e9e5 |
@@ -1,5 +1,6 @@
|
||||
packages/api/app/bundle.api.js
|
||||
packages/api/dist
|
||||
packages/api/@types
|
||||
packages/api/migrations
|
||||
|
||||
packages/crdt/dist
|
||||
@@ -24,7 +25,3 @@ packages/import-ynab5/**/node_modules/*
|
||||
packages/loot-core/**/node_modules/*
|
||||
packages/loot-core/**/lib-dist/*
|
||||
packages/loot-core/**/proto/*
|
||||
|
||||
packages/node-libofx/libofx.*.js
|
||||
packages/node-libofx/libofx/
|
||||
packages/node-libofx/OpenSP-*/
|
||||
|
||||
41
.eslintrc.js
@@ -38,12 +38,17 @@ module.exports = {
|
||||
extends: [
|
||||
'react-app',
|
||||
'plugin:react/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:import/typescript',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: { project: [path.join(__dirname, './tsconfig.json')] },
|
||||
reportUnusedDisableDirectives: true,
|
||||
globals: {
|
||||
globalThis: false,
|
||||
vi: true,
|
||||
},
|
||||
rules: {
|
||||
'prettier/prettier': 'warn',
|
||||
|
||||
@@ -52,7 +57,6 @@ module.exports = {
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
args: 'none',
|
||||
varsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
@@ -160,11 +164,17 @@ module.exports = {
|
||||
{ patterns: [...restrictedImportPatterns, ...restrictedImportColors] },
|
||||
],
|
||||
|
||||
'@typescript-eslint/ban-ts-comment': [
|
||||
'error',
|
||||
{ 'ts-ignore': 'allow-with-description' },
|
||||
],
|
||||
|
||||
// Rules disable during TS migration
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'prefer-const': 'warn',
|
||||
'prefer-spread': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'import/no-default-export': 'warn',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
@@ -200,6 +210,26 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['./packages/desktop-client/**/*'],
|
||||
excludedFiles: [
|
||||
'./packages/desktop-client/src/hooks/useNavigate.{ts,tsx}',
|
||||
],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'warn',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['react-router-dom'],
|
||||
importNames: ['useNavigate'],
|
||||
message: 'Please use Actual’s useNavigate() hook instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['./packages/loot-core/src/**/*'],
|
||||
rules: {
|
||||
@@ -238,6 +268,15 @@ module.exports = {
|
||||
'no-restricted-imports': ['off', { patterns: restrictedImportColors }],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'./packages/api/migrations/*',
|
||||
'./packages/loot-core/migrations/*',
|
||||
],
|
||||
rules: {
|
||||
'import/no-default-export': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
|
||||
2
.gitattributes
vendored
@@ -16,4 +16,4 @@ yarn.lock text eol=lf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpg binary
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -18,6 +18,18 @@ body:
|
||||
required: true
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: bank-sync-issue
|
||||
attributes:
|
||||
label: 'Is this related to GoCardless, Simplefin or another bank-sync provider?'
|
||||
description: 'Most issues with bank-sync providers are due to a lack of a custom bank-mapper (i.e. payee or other fields not coming through). In such cases you can create a custom bank mapper in [actual-server](https://github.com/actualbudget/actual-server/blob/master/src/app-gocardless/README.md) repository. Other likely issue is misconfigured server - in which case please reach out via the [community Discord](https://discord.gg/pRYNYr4W5A) to get support.'
|
||||
options:
|
||||
- label: 'I have checked my server logs and could not see any errors there'
|
||||
- label: 'I will be attaching my server logs to this issue'
|
||||
- label: 'I will be attaching my client-side (browser) logs to this issue'
|
||||
- label: 'I understand that this issue will be automatically closed if insufficient information is provided'
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Support
|
||||
url: https://discord.gg/pRYNYr4W5A
|
||||
about: Need help with something? Perhaps having issues setting up bank-sync with GoCardless or SimpleFin? Reach out to the community on Discord.
|
||||
|
||||
39
.github/workflows/e2e-test.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
needs: netlify
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.37.0-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.41.1-jammy
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
needs: netlify
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.37.0-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.41.1-jammy
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
@@ -66,38 +66,3 @@ jobs:
|
||||
name: desktop-client-test-results
|
||||
path: packages/desktop-client/test-results/
|
||||
retention-days: 30
|
||||
- uses: actions-ecosystem/action-add-labels@v1
|
||||
if: failure()
|
||||
with:
|
||||
labels: ':red_circle: VRT failing'
|
||||
- uses: actions-ecosystem/action-remove-labels@v1
|
||||
if: success()
|
||||
with:
|
||||
labels: ':red_circle: VRT failing'
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: VRT
|
||||
- name: Create comment if failed
|
||||
if: failure()
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
:wave: Looks like VRT (visual regression tests) are failing in this PR. This indicates a problem in the app. It could be either a bug in this PR or a visual change introduced by changing something.
|
||||
|
||||
To fix this: please follow [these instructions](https://github.com/actualbudget/actual/blob/master/packages/desktop-client/README.md#visual-regression) and review the output of the failing CI job to see the generated screenshots.
|
||||
|
||||
We look forward to reviewing this PR once all the CI jobs have passed successfully!
|
||||
edit-mode: replace
|
||||
- name: Update comment when CI job passes
|
||||
if: success() && steps.fc.outputs.comment-id != ''
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
body: The VRT tests have passed! Thank you!
|
||||
edit-mode: replace
|
||||
|
||||
50
.github/workflows/electron-master.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Electron
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
CI: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macos-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- if: ${{ startsWith(matrix.os, 'windows') }}
|
||||
run: pip.exe install setuptools
|
||||
- if: ${{ ! startsWith(matrix.os, 'windows') }}
|
||||
run: python3 -m pip install setuptools
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- name: Build Electron
|
||||
run: ./bin/package-electron
|
||||
env:
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
- name: Upload Build
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: actual-electron-${{ matrix.os }}
|
||||
path: |
|
||||
packages/desktop-electron/dist/*.dmg
|
||||
packages/desktop-electron/dist/*.exe
|
||||
packages/desktop-electron/dist/*.AppImage
|
||||
@@ -8,14 +8,11 @@ env:
|
||||
CI: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
10
.github/workflows/size-compare.yml
vendored
@@ -64,13 +64,17 @@ jobs:
|
||||
|
||||
- name: Strip content hashes from stats files
|
||||
run: |
|
||||
sed -i -E 's/index\.[0-9a-zA-Z_-]{8,}\./index./g' ./head/web-stats.json
|
||||
sed -i -E 's/\.[0-9a-zA-Z_-]{8,}\.chunk\././g' ./head/web-stats.json
|
||||
sed -i -E 's/\.[0-9a-f]{8,}\././g' ./head/*.json
|
||||
sed -i -E 's/index\.[0-9a-zA-Z_-]{8,}\./index./g' ./base/web-stats.json
|
||||
sed -i -E 's/\.[0-9a-zA-Z_-]{8,}\.chunk\././g' ./base/web-stats.json
|
||||
sed -i -E 's/\.[0-9a-f]{8,}\././g' ./base/*.json
|
||||
- uses: github/webpack-bundlesize-compare-action@v1.8.2
|
||||
- uses: twk3/rollup-size-compare-action@v1.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
current-stats-json-path: ./head/desktop-client-stats.json
|
||||
base-stats-json-path: ./base/desktop-client-stats.json
|
||||
current-stats-json-path: ./head/web-stats.json
|
||||
base-stats-json-path: ./base/web-stats.json
|
||||
title: desktop-client
|
||||
|
||||
- uses: github/webpack-bundlesize-compare-action@v1.8.2
|
||||
|
||||
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
!data/.gitkeep
|
||||
/data2
|
||||
packages/api/dist
|
||||
packages/api/@types
|
||||
packages/crdt/dist
|
||||
packages/desktop-electron/client-build
|
||||
packages/desktop-electron/.electron-symbols
|
||||
|
||||
2
.secret-tokens.example
Normal file
@@ -0,0 +1,2 @@
|
||||
export APPLE_ID=example@email.com
|
||||
export APPLE_APP_SPECIFIC_PASSWORD=password
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
ROOT=`dirname $0`
|
||||
RELEASE=""
|
||||
RELEASE_NOTES="" # TODO: figure out automation for release notes when we start publishing electron versions
|
||||
CI=${CI:-false}
|
||||
|
||||
cd "$ROOT/.."
|
||||
@@ -47,15 +46,13 @@ yarn workspace desktop-electron update-client
|
||||
cd packages/desktop-electron;
|
||||
yarn clean;
|
||||
|
||||
export npm_config_better_sqlite3_binary_host="https://static.actualbudget.com/prebuild/better-sqlite3"
|
||||
|
||||
if [ "$RELEASE" == "production" ]; then
|
||||
if [ -f ../../.secret-tokens ]; then
|
||||
source ../../.secret-tokens
|
||||
fi
|
||||
yarn build --publish always -c.releaseInfo.releaseNotes="$RELEASE_NOTES" --arm64 --x64
|
||||
yarn build --publish never --arm64 --x64
|
||||
|
||||
echo "\nCreated release with release notes \"$RELEASE_NOTES\""
|
||||
echo "\nCreated release"
|
||||
else
|
||||
SKIP_NOTARIZATION=true yarn build --publish never --x64
|
||||
fi
|
||||
|
||||
12
package.json
@@ -36,26 +36,28 @@
|
||||
"vrt": "yarn workspaces foreach --all --parallel --verbose run vrt",
|
||||
"rebuild-electron": "./node_modules/.bin/electron-rebuild -f -m ./packages/loot-core",
|
||||
"rebuild-node": "yarn workspace loot-core rebuild",
|
||||
"lint": "eslint . --max-warnings 0",
|
||||
"lint": "eslint . --max-warnings 0 --ext .js,.jsx,.ts,.tsx",
|
||||
"lint:verbose": "DEBUG=eslint:cli-engine eslint . --max-warnings 0",
|
||||
"typecheck": "yarn tsc",
|
||||
"typecheck": "yarn tsc && tsc-strict",
|
||||
"jq": "./node_modules/node-jq/bin/jq"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-react-app": "7.0.1",
|
||||
"eslint-import-resolver-typescript": "3.5.5",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-react": "7.32.2",
|
||||
"eslint-plugin-rulesdir": "^0.2.2",
|
||||
"node-jq": "^4.0.1",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"prettier": "2.8.2",
|
||||
"prettier": "3.2.4",
|
||||
"react-refresh": "^0.14.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"typescript": "^5.0.2"
|
||||
"typescript": "^5.0.2",
|
||||
"typescript-strict-plugin": "^2.2.2-beta.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
||||
@@ -99,6 +99,6 @@ class Query {
|
||||
}
|
||||
}
|
||||
|
||||
export default function q(table) {
|
||||
export function q(table) {
|
||||
return new Query({ table });
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// eslint-disable-next-line import/extensions
|
||||
import * as bundle from './app/bundle.api.js';
|
||||
import * as injected from './injected';
|
||||
import { validateNodeVersion } from './validateNodeVersion';
|
||||
|
||||
let actualApp;
|
||||
export const internal = bundle.lib;
|
||||
|
||||
// DEPRECATED: remove the next line in @actual-app/api v7
|
||||
export * as methods from './methods';
|
||||
|
||||
export * from './methods';
|
||||
export * as utils from './utils';
|
||||
|
||||
export async function init(config = {}) {
|
||||
if (actualApp) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateNodeVersion();
|
||||
|
||||
global.fetch = (...args) =>
|
||||
import('node-fetch').then(({ default: fetch }) => fetch(...args));
|
||||
|
||||
await bundle.init(config);
|
||||
actualApp = bundle.lib;
|
||||
|
||||
injected.override(bundle.lib.send);
|
||||
return bundle.lib;
|
||||
}
|
||||
|
||||
export async function shutdown() {
|
||||
if (actualApp) {
|
||||
await actualApp.send('sync');
|
||||
await actualApp.send('close-budget');
|
||||
actualApp = null;
|
||||
}
|
||||
}
|
||||
53
packages/api/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type {
|
||||
RequestInfo as FetchInfo,
|
||||
RequestInit as FetchInit,
|
||||
// @ts-ignore: false-positive commonjs module error on build until typescript 5.3
|
||||
} from 'node-fetch'; // with { 'resolution-mode': 'import' };
|
||||
|
||||
// loot-core types
|
||||
import type { InitConfig } from 'loot-core/server/main';
|
||||
|
||||
// @ts-ignore: bundle not available until we build it
|
||||
// eslint-disable-next-line import/extensions
|
||||
import * as bundle from './app/bundle.api.js';
|
||||
import * as injected from './injected';
|
||||
import { validateNodeVersion } from './validateNodeVersion';
|
||||
|
||||
let actualApp: null | typeof bundle.lib;
|
||||
export const internal = bundle.lib;
|
||||
|
||||
// DEPRECATED: remove the next line in @actual-app/api v7
|
||||
export * as methods from './methods';
|
||||
|
||||
export * from './methods';
|
||||
export * as utils from './utils';
|
||||
|
||||
export async function init(config: InitConfig = {}) {
|
||||
if (actualApp) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateNodeVersion();
|
||||
|
||||
if (!globalThis.fetch) {
|
||||
globalThis.fetch = (url: URL | RequestInfo, init?: RequestInit) => {
|
||||
return import('node-fetch').then(({ default: fetch }) =>
|
||||
fetch(url as unknown as FetchInfo, init as unknown as FetchInit),
|
||||
) as unknown as Promise<Response>;
|
||||
};
|
||||
}
|
||||
|
||||
await bundle.init(config);
|
||||
actualApp = bundle.lib;
|
||||
|
||||
injected.override(bundle.lib.send);
|
||||
return bundle.lib;
|
||||
}
|
||||
|
||||
export async function shutdown() {
|
||||
if (actualApp) {
|
||||
await actualApp.send('sync');
|
||||
await actualApp.send('close-budget');
|
||||
actualApp = null;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -272,4 +273,117 @@ describe('API CRUD operations', () => {
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
// apis: createPayee, getPayees, updatePayee, deletePayee
|
||||
test('Payees: successfully update payees', async () => {
|
||||
const payeeId1 = await api.createPayee({ name: 'test-payee1' });
|
||||
const payeeId2 = await api.createPayee({ name: 'test-payee2' });
|
||||
let payees = await api.getPayees();
|
||||
|
||||
// payees successfully created
|
||||
expect(payees).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: payeeId1,
|
||||
name: 'test-payee1',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: payeeId2,
|
||||
name: 'test-payee2',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await api.updatePayee(payeeId1, { name: 'test-updated-payee' });
|
||||
await api.deletePayee(payeeId2);
|
||||
|
||||
// confirm update and delete were successful
|
||||
payees = await api.getPayees();
|
||||
expect(payees).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: payeeId1,
|
||||
name: 'test-updated-payee',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
name: 'test-payee1',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
id: payeeId2,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
// apis: addTransactions, getTransactions, importTransactions, updateTransaction, deleteTransaction
|
||||
test('Transactions: successfully update transactions', async () => {
|
||||
const accountId = await api.createAccount({ name: 'test-account' }, 0);
|
||||
|
||||
let newTransaction = [
|
||||
{ date: '2023-11-03', imported_id: '11', amount: 100 },
|
||||
{ date: '2023-11-03', imported_id: '11', amount: 100 },
|
||||
];
|
||||
|
||||
const addResult = await api.addTransactions(accountId, newTransaction, {
|
||||
learnCategories: true,
|
||||
runTransfers: true,
|
||||
});
|
||||
expect(addResult).toBe('ok');
|
||||
|
||||
// confirm added transactions exist
|
||||
let transactions = await api.getTransactions(
|
||||
accountId,
|
||||
'2023-11-01',
|
||||
'2023-11-30',
|
||||
);
|
||||
expect(transactions).toEqual(
|
||||
expect.arrayContaining(
|
||||
newTransaction.map(trans => expect.objectContaining(trans)),
|
||||
),
|
||||
);
|
||||
expect(transactions).toHaveLength(2);
|
||||
|
||||
newTransaction = [
|
||||
{ date: '2023-12-03', imported_id: '11', amount: 100 },
|
||||
{ date: '2023-12-03', imported_id: '22', amount: 200 },
|
||||
];
|
||||
|
||||
const reconciled = await api.importTransactions(accountId, newTransaction);
|
||||
|
||||
// Expect it to reconcile and to have updated one of the previous transactions
|
||||
expect(reconciled.added).toHaveLength(1);
|
||||
expect(reconciled.updated).toHaveLength(1);
|
||||
|
||||
// confirm imported transactions exist
|
||||
transactions = await api.getTransactions(
|
||||
accountId,
|
||||
'2023-12-01',
|
||||
'2023-12-31',
|
||||
);
|
||||
expect(transactions).toEqual(
|
||||
expect.arrayContaining(
|
||||
newTransaction.map(trans => expect.objectContaining(trans)),
|
||||
),
|
||||
);
|
||||
expect(transactions).toHaveLength(2);
|
||||
|
||||
const idToUpdate = reconciled.added[0];
|
||||
const idToDelete = reconciled.updated[0];
|
||||
await api.updateTransaction(idToUpdate, { amount: 500 });
|
||||
await api.deleteTransaction(idToDelete);
|
||||
|
||||
// confirm updates and deletions work
|
||||
transactions = await api.getTransactions(
|
||||
accountId,
|
||||
'2023-12-01',
|
||||
'2023-12-31',
|
||||
);
|
||||
expect(transactions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: idToUpdate, amount: 500 }),
|
||||
expect.not.objectContaining({ id: idToDelete }),
|
||||
]),
|
||||
);
|
||||
expect(transactions).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
// @ts-strict-ignore
|
||||
import type { Handlers } from 'loot-core/src/types/handlers';
|
||||
|
||||
import * as injected from './injected';
|
||||
|
||||
export { default as q } from './app/query';
|
||||
export { q } from './app/query';
|
||||
|
||||
function send(name, args) {
|
||||
function send<K extends keyof Handlers, T extends Handlers[K]>(
|
||||
name: K,
|
||||
args?: Parameters<T>[0],
|
||||
): Promise<Awaited<ReturnType<T>>> {
|
||||
return injected.send(name, args);
|
||||
}
|
||||
|
||||
@@ -21,7 +27,7 @@ export async function loadBudget(budgetId) {
|
||||
return send('api/load-budget', { id: budgetId });
|
||||
}
|
||||
|
||||
export async function downloadBudget(syncId, { password } = {}) {
|
||||
export async function downloadBudget(syncId, { password }: { password? } = {}) {
|
||||
return send('api/download-budget', { syncId, password });
|
||||
}
|
||||
|
||||
@@ -79,10 +85,6 @@ export function getTransactions(accountId, startDate, endDate) {
|
||||
return send('api/transactions-get', { accountId, startDate, endDate });
|
||||
}
|
||||
|
||||
export function filterTransactions(accountId, text) {
|
||||
return send('api/transactions-filter', { accountId, text });
|
||||
}
|
||||
|
||||
export function updateTransaction(id, fields) {
|
||||
return send('api/transaction-update', { id, fields });
|
||||
}
|
||||
@@ -95,7 +97,7 @@ export function getAccounts() {
|
||||
return send('api/accounts-get');
|
||||
}
|
||||
|
||||
export function createAccount(account, initialBalance) {
|
||||
export function createAccount(account, initialBalance?) {
|
||||
return send('api/account-create', { account, initialBalance });
|
||||
}
|
||||
|
||||
@@ -103,7 +105,7 @@ export function updateAccount(id, fields) {
|
||||
return send('api/account-update', { id, fields });
|
||||
}
|
||||
|
||||
export function closeAccount(id, transferAccountId, transferCategoryId) {
|
||||
export function closeAccount(id, transferAccountId?, transferCategoryId?) {
|
||||
return send('api/account-close', {
|
||||
id,
|
||||
transferAccountId,
|
||||
@@ -127,7 +129,7 @@ export function updateCategoryGroup(id, fields) {
|
||||
return send('api/category-group-update', { id, fields });
|
||||
}
|
||||
|
||||
export function deleteCategoryGroup(id, transferCategoryId) {
|
||||
export function deleteCategoryGroup(id, transferCategoryId?) {
|
||||
return send('api/category-group-delete', { id, transferCategoryId });
|
||||
}
|
||||
|
||||
@@ -143,7 +145,7 @@ export function updateCategory(id, fields) {
|
||||
return send('api/category-update', { id, fields });
|
||||
}
|
||||
|
||||
export function deleteCategory(id, transferCategoryId) {
|
||||
export function deleteCategory(id, transferCategoryId?) {
|
||||
return send('api/category-delete', { id, transferCategoryId });
|
||||
}
|
||||
|
||||
@@ -7,30 +7,32 @@
|
||||
"node": ">=18.12.0"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"types": "@types/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build:app": "yarn workspace loot-core build:api",
|
||||
"build:node": "tsc --p tsconfig.dist.json",
|
||||
"build:node": "tsc --p tsconfig.dist.json && tsc-alias -p tsconfig.dist.json",
|
||||
"build:migrations": "cp migrations/*.sql dist/migrations",
|
||||
"build:default-db": "cp default-db.sqlite dist/",
|
||||
"build": "rm -rf dist && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db",
|
||||
"test": "yarn run build:app && jest -c jest.config.js"
|
||||
"build": "yarn run clean && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db",
|
||||
"test": "yarn run build:app && jest -c jest.config.js",
|
||||
"clean": "rm -rf dist @types"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^9.1.1",
|
||||
"better-sqlite3": "^9.2.2",
|
||||
"compare-versions": "^6.1.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/core": "^1.3.82",
|
||||
"@swc/jest": "^0.2.29",
|
||||
"@swc/core": "^1.3.105",
|
||||
"@swc/jest": "^0.2.31",
|
||||
"@types/jest": "^27.5.0",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"jest": "^27.0.0",
|
||||
"tsc-alias": "^1.8.8",
|
||||
"typescript": "^5.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
"compilerOptions": {
|
||||
// Using ES2021 because that’s the newest version where
|
||||
// the latest Node 16.x release supports all of the features
|
||||
"target": "es2021",
|
||||
"target": "ES2021",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node16",
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"declarationDir": "@types",
|
||||
"paths": {
|
||||
"loot-core/*": ["./@types/loot-core/*"],
|
||||
}
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["dist"]
|
||||
"exclude": ["**/node_modules/*", "dist", "@types"]
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/core": "^1.3.82",
|
||||
"@swc/jest": "^0.2.29",
|
||||
"@swc/core": "^1.3.105",
|
||||
"@swc/jest": "^0.2.31",
|
||||
"@types/jest": "^27.5.0",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"jest": "^27.0.0",
|
||||
|
||||
@@ -93,7 +93,7 @@ export function diff(trie1: TrieNode, trie2: TrieNode): number | null {
|
||||
const keys = [...keyset.values()];
|
||||
keys.sort();
|
||||
|
||||
let diffkey = null;
|
||||
let diffkey: null | '0' | '1' | '2' = null;
|
||||
|
||||
// Traverse down the trie through keys that aren't the same. We
|
||||
// traverse down the keys in order. Stop in two cases: either one
|
||||
|
||||
@@ -267,10 +267,10 @@ export class Timestamp {
|
||||
lNew === lOld && lNew === lMsg
|
||||
? Math.max(cOld, cMsg) + 1
|
||||
: lNew === lOld
|
||||
? cOld + 1
|
||||
: lNew === lMsg
|
||||
? cMsg + 1
|
||||
: 0;
|
||||
? cOld + 1
|
||||
: lNew === lMsg
|
||||
? cMsg + 1
|
||||
: 0;
|
||||
|
||||
// check the result for drift and counter overflow
|
||||
if (lNew - phys > config.maxDrift) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"compilerOptions": {
|
||||
// Using ES2021 because that’s the newest version where
|
||||
// the latest Node 16.x release supports all of the features
|
||||
"target": "es2021",
|
||||
"target": "ES2021",
|
||||
"module": "CommonJS",
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
|
||||
2
packages/desktop-client/.gitignore
vendored
@@ -10,11 +10,13 @@ test-results
|
||||
# production
|
||||
build
|
||||
build-stats
|
||||
stats.json
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log
|
||||
.swc
|
||||
|
||||
*kcab.*
|
||||
public/kcab
|
||||
|
||||
@@ -32,25 +32,27 @@ Prerequisites:
|
||||
|
||||
#### Running against the local server
|
||||
|
||||
First start the dev server:
|
||||
First start a dev instance:
|
||||
|
||||
```sh
|
||||
HTTPS=true yarn start
|
||||
```
|
||||
Note the network IP address and port the dev instance is listening on.
|
||||
|
||||
Next, navigate to the root of your project folder, run the standartised docker container, and launch the visual regression tests from within it.
|
||||
|
||||
```sh
|
||||
# Run docker container
|
||||
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.37.0-jammy /bin/bash
|
||||
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-jammy /bin/bash
|
||||
|
||||
# If you recieve an error such as "docker: invalid reference format", please instead use the following command:
|
||||
docker run --rm --network host -v ${pwd}:/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.37.0-jammy /bin/bash
|
||||
# If you receive an error such as "docker: invalid reference format", please instead use the following command:
|
||||
docker run --rm --network host -v ${pwd}:/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-jammy /bin/bash
|
||||
|
||||
# Run the VRT tests: important - they MUST be ran against a HTTPS server
|
||||
E2E_START_URL=https://192.168.0.178:3001 yarn vrt
|
||||
# Run the VRT tests: important - they MUST be ran against a HTTPS server. Use the ip and port noted earlier
|
||||
E2E_START_URL=https://ip:port yarn vrt
|
||||
|
||||
# To update snapshots, use the following command:
|
||||
E2E_START_URL=https://192.168.0.178:3001 yarn vrt --update-snapshots
|
||||
E2E_START_URL=https://ip:port yarn vrt --update-snapshots
|
||||
```
|
||||
|
||||
#### Running against a remote server
|
||||
|
||||
@@ -8,7 +8,6 @@ echo "Building the browser..."
|
||||
rm -fr build
|
||||
|
||||
export IS_GENERIC_BROWSER=1
|
||||
export INLINE_RUNTIME_CHUNK=false
|
||||
export REACT_APP_BACKEND_WORKER_HASH=`ls "$ROOT"/../public/kcab/kcab.worker.*.js | sed 's/.*kcab\.worker\.\(.*\)\.js/\1/'`
|
||||
|
||||
yarn build
|
||||
@@ -16,4 +15,4 @@ yarn build
|
||||
rm -fr build-stats
|
||||
mkdir build-stats
|
||||
mv build/kcab/stats.json build-stats/loot-core-stats.json
|
||||
mv build/stats.json build-stats/desktop-client-stats.json
|
||||
mv ./stats.json build-stats/web-stats.json
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
loaderByName,
|
||||
removeLoaders,
|
||||
addAfterLoader,
|
||||
addPlugins,
|
||||
} = require('@craco/craco');
|
||||
const chokidar = require('chokidar');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const { IgnorePlugin } = require('webpack');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
|
||||
if (process.env.CI) {
|
||||
process.env.DISABLE_ESLINT_PLUGIN = 'true';
|
||||
}
|
||||
|
||||
// Forward Netlify env variables
|
||||
if (process.env.REVIEW_ID) {
|
||||
process.env.REACT_APP_REVIEW_ID = process.env.REVIEW_ID;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
webpack: {
|
||||
configure: (webpackConfig, { env, paths }) => {
|
||||
webpackConfig.mode =
|
||||
process.env.NODE_ENV === 'development' ? 'development' : 'production';
|
||||
|
||||
// swc-loader
|
||||
addAfterLoader(webpackConfig, loaderByName('babel-loader'), {
|
||||
test: /\.m?[tj]sx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: require.resolve('swc-loader'),
|
||||
});
|
||||
|
||||
// remove the babel loaders
|
||||
removeLoaders(webpackConfig, loaderByName('babel-loader'));
|
||||
|
||||
addPlugins(webpackConfig, [
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'disabled',
|
||||
generateStatsFile: true,
|
||||
}),
|
||||
// Pikaday throws a warning if Moment.js is not installed however it doesn't
|
||||
// actually require it to be installed. As we don't use Moment.js ourselves
|
||||
// then we can just silence this warning.
|
||||
new IgnorePlugin({
|
||||
contextRegExp: /pikaday$/,
|
||||
resourceRegExp: /moment$/,
|
||||
}),
|
||||
]);
|
||||
|
||||
webpackConfig.resolve.extensions = [
|
||||
'.web.js',
|
||||
'.web.jsx',
|
||||
'.web.ts',
|
||||
'.web.tsx',
|
||||
'.js',
|
||||
'.jsx',
|
||||
'.ts',
|
||||
'.tsx',
|
||||
...webpackConfig.resolve.extensions,
|
||||
];
|
||||
|
||||
if (process.env.IS_GENERIC_BROWSER) {
|
||||
webpackConfig.resolve.extensions = [
|
||||
'.browser.js',
|
||||
'.browser.jsx',
|
||||
'.browser.ts',
|
||||
'.browser.tsx',
|
||||
...webpackConfig.resolve.extensions,
|
||||
];
|
||||
}
|
||||
|
||||
webpackConfig.optimization = {
|
||||
...webpackConfig.optimization,
|
||||
minimize:
|
||||
process.env.CI === 'true' || process.env.NODE_ENV !== 'development',
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: TerserPlugin.swcMinify,
|
||||
// `terserOptions` options will be passed to `swc` (`@swc/core`)
|
||||
// Link to options - https://swc.rs/docs/config-js-minify
|
||||
terserOptions: {
|
||||
compress: false,
|
||||
mangle: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
return webpackConfig;
|
||||
},
|
||||
},
|
||||
devServer: (devServerConfig, { env, paths, proxy, allowedHost }) => {
|
||||
devServerConfig.onBeforeSetupMiddleware = server => {
|
||||
chokidar
|
||||
.watch([
|
||||
path.resolve('../loot-core/lib-dist/*.js'),
|
||||
path.resolve('../loot-core/lib-dist/browser/*.js'),
|
||||
])
|
||||
.on('all', function () {
|
||||
for (const ws of server.webSocketServer.clients) {
|
||||
ws.send(JSON.stringify({ type: 'static-changed' }));
|
||||
}
|
||||
});
|
||||
};
|
||||
devServerConfig.headers = {
|
||||
...devServerConfig.headers,
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
};
|
||||
|
||||
return devServerConfig;
|
||||
},
|
||||
};
|
||||
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
@@ -49,7 +49,7 @@ test.describe('Mobile', () => {
|
||||
test('opens the accounts page and asserts on balances', async () => {
|
||||
const accountsPage = await navigation.goToAccountsPage();
|
||||
|
||||
const account = await accountsPage.getNthAccount(0);
|
||||
const account = await accountsPage.getNthAccount(1);
|
||||
|
||||
await expect(account.name).toHaveText('Ally Savings');
|
||||
await expect(account.balance).toHaveText('7,653.00');
|
||||
@@ -58,7 +58,7 @@ test.describe('Mobile', () => {
|
||||
|
||||
test('opens individual account page and checks that filtering is working', async () => {
|
||||
const accountsPage = await navigation.goToAccountsPage();
|
||||
const accountPage = await accountsPage.openNthAccount(1);
|
||||
const accountPage = await accountsPage.openNthAccount(0);
|
||||
|
||||
await expect(accountPage.heading).toHaveText('Bank of America');
|
||||
expect(await accountPage.getBalance()).toBeGreaterThan(0);
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 571 KiB After Width: | Height: | Size: 574 KiB |
|
Before Width: | Height: | Size: 646 KiB After Width: | Height: | Size: 649 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
@@ -8,25 +8,25 @@
|
||||
/>
|
||||
<title>Actual</title>
|
||||
<link rel="canonical" href="/" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="%PUBLIC_URL%/apple-touch-icon.png"
|
||||
href="/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="%PUBLIC_URL%/favicon-32x32.png"
|
||||
href="/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="%PUBLIC_URL%/favicon-16x16.png"
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" />
|
||||
<link rel="manifest" href="/site.webmanifest" crossorigin="use-credentials"/>
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
@@ -102,5 +102,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,19 +6,19 @@
|
||||
"build"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@craco/types": "^7.1.0",
|
||||
"@juggle/resize-observer": "^3.1.2",
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@playwright/test": "^1.41.1",
|
||||
"@reach/listbox": "^0.18.0",
|
||||
"@react-aria/focus": "^3.14.0",
|
||||
"@react-aria/listbox": "^3.10.1",
|
||||
"@react-aria/utils": "^3.19.0",
|
||||
"@react-stately/collections": "^3.10.0",
|
||||
"@react-stately/list": "^3.9.1",
|
||||
"@rollup/plugin-inject": "^5.0.5",
|
||||
"@svgr/cli": "^8.0.1",
|
||||
"@swc/core": "^1.3.82",
|
||||
"@swc/helpers": "^0.5.1",
|
||||
"@swc/core": "^1.3.105",
|
||||
"@swc/helpers": "^0.5.3",
|
||||
"@swc/plugin-react-remove-properties": "^1.5.108",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/react": "^18.2.0",
|
||||
@@ -26,8 +26,10 @@
|
||||
"@types/react-modal": "^3.16.0",
|
||||
"@types/react-redux": "^7.1.25",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@types/webpack-bundle-analyzer": "^4.6.0",
|
||||
"@types/webpack-bundle-analyzer": "^4.6.3",
|
||||
"@use-gesture/react": "^10.3.0",
|
||||
"@vitejs/plugin-basic-ssl": "^1.1.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"date-fns": "^2.29.3",
|
||||
@@ -41,7 +43,7 @@
|
||||
"jest-watch-typeahead": "^2.2.2",
|
||||
"mdast-util-newline-to-break": "^2.0.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"pikaday": "1.8.0",
|
||||
"pikaday": "1.8.2",
|
||||
"react": "18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
@@ -52,7 +54,6 @@
|
||||
"react-modal": "3.16.1",
|
||||
"react-redux": "7.2.1",
|
||||
"react-router-dom": "6.11.2",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-simple-pull-to-refresh": "^1.3.3",
|
||||
"react-spring": "^9.7.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
@@ -60,22 +61,27 @@
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rollup-plugin-visualizer": "^5.11.0",
|
||||
"sass": "^1.63.6",
|
||||
"swc-loader": "^0.2.3",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"typescript": "^5.0.2",
|
||||
"uuid": "^9.0.0",
|
||||
"victory": "^36.6.8",
|
||||
"webpack-bundle-analyzer": "^4.9.1",
|
||||
"vite": "^5.0.12",
|
||||
"vite-tsconfig-paths": "^4.3.1",
|
||||
"vitest": "^1.2.1",
|
||||
"webpack-bundle-analyzer": "^4.10.1",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env PORT=3001 craco start",
|
||||
"start": "cross-env PORT=3001 vite",
|
||||
"start:browser": "cross-env ./bin/watch-browser",
|
||||
"watch": "cross-env BROWSER=none yarn start",
|
||||
"build": "cross-env INLINE_RUNTIME_CHUNK=false craco build",
|
||||
"build": "vite build",
|
||||
"build:browser": "cross-env ./bin/build-browser",
|
||||
"generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --typescript --expand-props start -d . .",
|
||||
"test": "craco test",
|
||||
"generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --template template.ts --index-template index-template.ts --typescript --expand-props start -d . .",
|
||||
"test": "vitest",
|
||||
"e2e": "npx playwright test --browser=chromium",
|
||||
"vrt": "cross-env VRT=true npx playwright test --browser=chromium"
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ expect.extend({
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
// eslint-disable-next-line import/no-unused-modules, import/no-default-export
|
||||
export default defineConfig({
|
||||
timeout: 20000, // 20 seconds
|
||||
retries: 1,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"name": "Actual",
|
||||
"short_name": "Actual",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
@@ -15,5 +15,6 @@
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
"display": "standalone",
|
||||
"start_url": "."
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import { type ReactNode, createContext, useContext } from 'react';
|
||||
|
||||
import { useViewportSize } from '@react-aria/utils';
|
||||
|
||||
@@ -51,7 +51,7 @@ global.Actual = {
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
openFileDialog: async ({ filters = [], properties }) => {
|
||||
openFileDialog: async ({ filters = [] }) => {
|
||||
return new Promise(resolve => {
|
||||
let createdElement = false;
|
||||
// Attempt to reuse an already-created file input.
|
||||
@@ -91,7 +91,7 @@ global.Actual = {
|
||||
.uploadFile(filename, ev.target.result)
|
||||
.then(() => resolve([filepath]));
|
||||
};
|
||||
reader.onerror = function (ev) {
|
||||
reader.onerror = function () {
|
||||
alert('Error reading file');
|
||||
};
|
||||
}
|
||||
@@ -107,7 +107,7 @@ global.Actual = {
|
||||
});
|
||||
},
|
||||
|
||||
saveFile: (contents, defaultFilename, dialogTitle) => {
|
||||
saveFile: (contents, defaultFilename) => {
|
||||
const temp = document.createElement('a');
|
||||
temp.style = 'display: none';
|
||||
temp.download = defaultFilename;
|
||||
@@ -121,10 +121,11 @@ global.Actual = {
|
||||
openURLInBrowser: url => {
|
||||
window.open(url, '_blank');
|
||||
},
|
||||
onEventFromMain: (type, handler) => {},
|
||||
onEventFromMain: () => {},
|
||||
applyAppUpdate: () => {},
|
||||
updateAppMenu: isBudgetOpen => {},
|
||||
updateAppMenu: () => {},
|
||||
|
||||
ipcConnect: () => {},
|
||||
getServerSocket: async () => {
|
||||
return worker;
|
||||
},
|
||||
|
||||
14
packages/desktop-client/src/build-shims.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const global = globalThis || this || self;
|
||||
|
||||
const process = {
|
||||
env: {
|
||||
...import.meta.env,
|
||||
NODE_ENV: import.meta.env.MODE,
|
||||
PUBLIC_URL: import.meta.env.BASE_URL.slice(0, -1),
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
export { process };
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
export { global };
|
||||
@@ -1,11 +1,12 @@
|
||||
// @ts-strict-ignore
|
||||
import React from 'react';
|
||||
|
||||
import { keyframes } from 'glamor';
|
||||
|
||||
import Refresh from '../icons/v1/Refresh';
|
||||
import { SvgRefresh } from '../icons/v1';
|
||||
import { type CSSProperties } from '../style';
|
||||
|
||||
import View from './common/View';
|
||||
import { View } from './common/View';
|
||||
|
||||
const spin = keyframes({
|
||||
'0%': { transform: 'rotateZ(0deg)' },
|
||||
@@ -19,7 +20,7 @@ type AnimatedRefreshProps = {
|
||||
height?: number;
|
||||
};
|
||||
|
||||
export default function AnimatedRefresh({
|
||||
export function AnimatedRefresh({
|
||||
animating,
|
||||
iconStyle,
|
||||
width,
|
||||
@@ -29,7 +30,7 @@ export default function AnimatedRefresh({
|
||||
<View
|
||||
style={{ animation: animating ? `${spin} 1s infinite linear` : null }}
|
||||
>
|
||||
<Refresh
|
||||
<SvgRefresh
|
||||
width={width ? width : 14}
|
||||
height={height ? height : 14}
|
||||
style={iconStyle}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
ErrorBoundary,
|
||||
@@ -14,20 +15,20 @@ import {
|
||||
import { type GlobalPrefs } from 'loot-core/src/types/prefs';
|
||||
|
||||
import { useActions } from '../hooks/useActions';
|
||||
import installPolyfills from '../polyfills';
|
||||
import { installPolyfills } from '../polyfills';
|
||||
import { ResponsiveProvider } from '../ResponsiveProvider';
|
||||
import { styles, hasHiddenScrollbars, ThemeStyle } from '../style';
|
||||
|
||||
import AppBackground from './AppBackground';
|
||||
import View from './common/View';
|
||||
import DevelopmentTopBar from './DevelopmentTopBar';
|
||||
import FatalError from './FatalError';
|
||||
import FinancesApp from './FinancesApp';
|
||||
import ManagementApp from './manager/ManagementApp';
|
||||
import MobileWebMessage from './MobileWebMessage';
|
||||
import UpdateNotification from './UpdateNotification';
|
||||
import { AppBackground } from './AppBackground';
|
||||
import { View } from './common/View';
|
||||
import { DevelopmentTopBar } from './DevelopmentTopBar';
|
||||
import { FatalError } from './FatalError';
|
||||
import { FinancesApp } from './FinancesApp';
|
||||
import { ManagementApp } from './manager/ManagementApp';
|
||||
import { MobileWebMessage } from './MobileWebMessage';
|
||||
import { UpdateNotification } from './UpdateNotification';
|
||||
|
||||
type AppProps = {
|
||||
type AppInnerProps = {
|
||||
budgetId: string;
|
||||
cloudFileId: string;
|
||||
loadingText: string;
|
||||
@@ -40,14 +41,14 @@ type AppProps = {
|
||||
loadGlobalPrefs: () => Promise<GlobalPrefs>;
|
||||
};
|
||||
|
||||
function App({
|
||||
function AppInner({
|
||||
budgetId,
|
||||
cloudFileId,
|
||||
loadingText,
|
||||
loadBudget,
|
||||
closeBudget,
|
||||
loadGlobalPrefs,
|
||||
}: AppProps) {
|
||||
}: AppInnerProps) {
|
||||
const [initializing, setInitializing] = useState(true);
|
||||
const { showBoundary: showErrorBoundary } = useErrorBoundary();
|
||||
|
||||
@@ -121,7 +122,7 @@ function ErrorFallback({ error }: FallbackProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function AppWrapper() {
|
||||
export function App() {
|
||||
const budgetId = useSelector(
|
||||
state => state.prefs.local && state.prefs.local.id,
|
||||
);
|
||||
@@ -178,7 +179,7 @@ function AppWrapper() {
|
||||
{process.env.REACT_APP_REVIEW_ID && !Platform.isPlaywright && (
|
||||
<DevelopmentTopBar />
|
||||
)}
|
||||
<App
|
||||
<AppInner
|
||||
budgetId={budgetId}
|
||||
cloudFileId={cloudFileId}
|
||||
loadingText={loadingText}
|
||||
@@ -193,5 +194,3 @@ function AppWrapper() {
|
||||
</ResponsiveProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppWrapper;
|
||||
|
||||
@@ -2,19 +2,22 @@ import React from 'react';
|
||||
|
||||
import { css } from 'glamor';
|
||||
|
||||
import AnimatedLoading from '../icons/AnimatedLoading';
|
||||
import { AnimatedLoading } from '../icons/AnimatedLoading';
|
||||
import { theme } from '../style';
|
||||
|
||||
import Background from './Background';
|
||||
import Block from './common/Block';
|
||||
import View from './common/View';
|
||||
import { Background } from './Background';
|
||||
import { Block } from './common/Block';
|
||||
import { View } from './common/View';
|
||||
|
||||
type AppBackgroundProps = {
|
||||
initializing?: boolean;
|
||||
loadingText?: string;
|
||||
};
|
||||
|
||||
function AppBackground({ initializing, loadingText }: AppBackgroundProps) {
|
||||
export function AppBackground({
|
||||
initializing,
|
||||
loadingText,
|
||||
}: AppBackgroundProps) {
|
||||
return (
|
||||
<>
|
||||
<Background />
|
||||
@@ -41,5 +44,3 @@ function AppBackground({ initializing, loadingText }: AppBackgroundProps) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppBackground;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { theme } from '../style';
|
||||
|
||||
import { LoadComponent } from './util/LoadComponent';
|
||||
|
||||
export default function Background() {
|
||||
export function Background() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -4,11 +4,11 @@ import { useTransition, animated } from 'react-spring';
|
||||
|
||||
import { theme, styles } from '../style';
|
||||
|
||||
import AnimatedRefresh from './AnimatedRefresh';
|
||||
import Text from './common/Text';
|
||||
import View from './common/View';
|
||||
import { AnimatedRefresh } from './AnimatedRefresh';
|
||||
import { Text } from './common/Text';
|
||||
import { View } from './common/View';
|
||||
|
||||
export default function BankSyncStatus() {
|
||||
export function BankSyncStatus() {
|
||||
const accountsSyncing = useSelector(state => state.account.accountsSyncing);
|
||||
|
||||
const name = accountsSyncing
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { theme } from '../style';
|
||||
|
||||
import ExternalLink from './common/ExternalLink';
|
||||
import View from './common/View';
|
||||
import { ExternalLink } from './common/ExternalLink';
|
||||
import { View } from './common/View';
|
||||
|
||||
export default function DevelopmentTopBar() {
|
||||
export function DevelopmentTopBar() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// @ts-strict-ignore
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import Block from './common/Block';
|
||||
import Button from './common/Button';
|
||||
import ExternalLink from './common/ExternalLink';
|
||||
import LinkButton from './common/LinkButton';
|
||||
import Modal from './common/Modal';
|
||||
import Paragraph from './common/Paragraph';
|
||||
import Stack from './common/Stack';
|
||||
import Text from './common/Text';
|
||||
import View from './common/View';
|
||||
import { Block } from './common/Block';
|
||||
import { Button } from './common/Button';
|
||||
import { ExternalLink } from './common/ExternalLink';
|
||||
import { LinkButton } from './common/LinkButton';
|
||||
import { Modal } from './common/Modal';
|
||||
import { Paragraph } from './common/Paragraph';
|
||||
import { Stack } from './common/Stack';
|
||||
import { Text } from './common/Text';
|
||||
import { View } from './common/View';
|
||||
import { Checkbox } from './forms';
|
||||
|
||||
type AppError = Error & {
|
||||
@@ -130,7 +131,7 @@ function SharedArrayBufferOverride() {
|
||||
>
|
||||
<Checkbox
|
||||
checked={understand}
|
||||
onChange={_ => setUnderstand(!understand)}
|
||||
onChange={() => setUnderstand(!understand)}
|
||||
/>{' '}
|
||||
I understand the risks, run Actual in the unsupported fallback mode
|
||||
</label>
|
||||
@@ -151,7 +152,7 @@ function SharedArrayBufferOverride() {
|
||||
);
|
||||
}
|
||||
|
||||
function FatalError({ buttonText, error }: FatalErrorProps) {
|
||||
export function FatalError({ buttonText, error }: FatalErrorProps) {
|
||||
const [showError, setShowError] = useState(false);
|
||||
|
||||
const showSimpleRender = 'type' in error && error.type === 'app-init-failure';
|
||||
@@ -185,5 +186,3 @@ function FatalError({ buttonText, error }: FatalErrorProps) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default FatalError;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import React, { type ReactElement, useEffect, useMemo } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend as Backend } from 'react-dnd-html5-backend';
|
||||
@@ -5,7 +6,6 @@ import {
|
||||
Route,
|
||||
Routes,
|
||||
Navigate,
|
||||
useNavigate,
|
||||
BrowserRouter,
|
||||
useLocation,
|
||||
useHref,
|
||||
@@ -16,30 +16,31 @@ import hotkeys from 'hotkeys-js';
|
||||
import { AccountsProvider } from 'loot-core/src/client/data-hooks/accounts';
|
||||
import { PayeesProvider } from 'loot-core/src/client/data-hooks/payees';
|
||||
import { SpreadsheetProvider } from 'loot-core/src/client/SpreadsheetProvider';
|
||||
import checkForUpdateNotification from 'loot-core/src/client/update-notification';
|
||||
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 { useNavigate } from '../hooks/useNavigate';
|
||||
import { useResponsive } from '../ResponsiveProvider';
|
||||
import { theme } from '../style';
|
||||
import { ExposeNavigate } from '../util/router-tools';
|
||||
import { getIsOutdated, getLatestVersion } from '../util/versions';
|
||||
|
||||
import BankSyncStatus from './BankSyncStatus';
|
||||
import { BankSyncStatus } from './BankSyncStatus';
|
||||
import { BudgetMonthCountProvider } from './budget/BudgetMonthCountContext';
|
||||
import View from './common/View';
|
||||
import GlobalKeys from './GlobalKeys';
|
||||
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 { MobileNavTabs } from './mobile/MobileNavTabs';
|
||||
import { Modals } from './Modals';
|
||||
import { Notifications } from './Notifications';
|
||||
import { ManagePayeesPage } from './payees/ManagePayeesPage';
|
||||
import Reports from './reports';
|
||||
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';
|
||||
import { ScrollProvider } from './ScrollProvider';
|
||||
import { Settings } from './settings';
|
||||
import { FloatableSidebar, SidebarProvider } from './sidebar';
|
||||
import { Titlebar, TitlebarProvider } from './Titlebar';
|
||||
import { TransactionEdit } from './transactions/MobileTransaction';
|
||||
|
||||
function NarrowNotSupported({
|
||||
@@ -92,7 +93,7 @@ function RouterBehaviors({ getAccounts }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function FinancesApp() {
|
||||
function FinancesAppWithoutContext() {
|
||||
const actions = useActions();
|
||||
useEffect(() => {
|
||||
// The default key handler scope
|
||||
@@ -256,8 +257,8 @@ function FinancesApp() {
|
||||
);
|
||||
}
|
||||
|
||||
export default function FinancesAppWithContext() {
|
||||
const app = useMemo(() => <FinancesApp />, []);
|
||||
export function FinancesApp() {
|
||||
const app = useMemo(() => <FinancesAppWithoutContext />, []);
|
||||
|
||||
return (
|
||||
<SpreadsheetProvider>
|
||||
|
||||