From 45517bcfcbe7d8fc619aa1bcaf81ff706e65143d Mon Sep 17 00:00:00 2001 From: Morre Date: Mon, 14 Aug 2023 21:59:46 +0200 Subject: [PATCH 1/3] feat: add YNAP parsing to transaction import This adds YNAP parsing to transaction importing, removing the need to parse the file in the YNAP web app before uploading it to Envelope Zero. Since the jschardet dependency for ynap-parsers does not support strict mode, we need to patch its sloppiness, too. Co-Authored-By: Fynn Heintz Co-Authored-By: Morre --- .github/workflows/workflow.yml | 15 +- .npmrc | 1 + CONTRIBUTING.md | 7 +- Dockerfile | 6 +- cypress.config.ts | 1 + cypress/e2e/transaction-import.cy.ts | 44 +- ...sactions-parsed.csv => comdirect-ynap.csv} | 2 +- cypress/fixtures/comdirect.csv | 11 + cypress/fixtures/transactions-no-parser.csv | 5 + docker-compose-production.yml | 6 + package-lock.json | 796 +++++++++++++++++- package.json | 7 +- patches/jschardet+3.0.0.patch | 88 ++ src/components/TransactionImport/Form.tsx | 77 +- src/translations/en.json | 11 +- vite.config.ts | 5 + 16 files changed, 990 insertions(+), 92 deletions(-) create mode 100644 .npmrc rename cypress/fixtures/{transactions-parsed.csv => comdirect-ynap.csv} (80%) create mode 100644 cypress/fixtures/comdirect.csv create mode 100644 cypress/fixtures/transactions-no-parser.csv create mode 100644 patches/jschardet+3.0.0.patch diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index f31bdb87..e6857cff 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -14,15 +14,21 @@ env: jobs: pre-commit: runs-on: ubuntu-latest + permissions: + packages: read + steps: - uses: actions/checkout@v3.6.0 - name: Cache dependencies uses: actions/setup-node@v3.8.1 with: - node-version: '16' + node-version: '20' cache: 'npm' + - name: set npm auth + run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc + - name: install node modules run: npm ci @@ -30,6 +36,8 @@ jobs: cypress: runs-on: ubuntu-latest + permissions: + packages: read # Current backend version as service container services: @@ -44,6 +52,9 @@ jobs: steps: - uses: actions/checkout@v3.6.0 + - name: set npm auth + run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc + - uses: cypress-io/github-action@v6.4.0 with: start: npm run start-ci @@ -97,9 +108,9 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@v4.1.1 with: - context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | VITE_VERSION=${{ github.ref_name }} + GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..13a79428 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@envelope-zero:registry=https://npm.pkg.github.com diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 432a0b9a..89c36d0d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,11 +4,12 @@ Contributions are welcome. Please note the [Code of Conduct](CODE_OF_CONDUCT.md) ## Tool & Repository setup -You will need the following tools: +You will need to: -- [pre-commit](https://pre-commit.com/) +- Install [pre-commit](https://pre-commit.com/) +- Set up a GitHub Personal Access Token to read from the GitHub package repository. Follow the steps in https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry#authenticating-with-a-personal-access-token. -Once those are installed, run `make setup` to perform the repository setup. +Once these steps are done, run `make setup` to perform the repository setup. ## Development server diff --git a/Dockerfile b/Dockerfile index f422bcda..d2af94ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,11 @@ WORKDIR /app # copy package.json first to avoid unnecessary npm install when other files change # Unless packages change, this layer will be cached COPY package.json package-lock.json /app/ -RUN npm install +COPY patches /app/patches + +ARG GITHUB_TOKEN +RUN echo "//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}" >> ~/.npmrc && \ + npm install --production # Copy app files COPY src /app/src diff --git a/cypress.config.ts b/cypress.config.ts index 8e47cfff..5708847b 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'cypress' export default defineConfig({ + video: true, e2e: { baseUrl: 'http://localhost:3000', supportFile: 'cypress/support/e2e.ts', diff --git a/cypress/e2e/transaction-import.cy.ts b/cypress/e2e/transaction-import.cy.ts index 66797f2f..54806df1 100644 --- a/cypress/e2e/transaction-import.cy.ts +++ b/cypress/e2e/transaction-import.cy.ts @@ -35,9 +35,7 @@ describe('Transaction Import', () => { cy.contains('My Other Account') cy.contains('My Account').click() - cy.getInputFor('File').selectFile( - 'cypress/fixtures/transactions-parsed.csv' - ) + cy.getInputFor('File').selectFile('cypress/fixtures/comdirect.csv') cy.clickAndWait('Submit') @@ -163,11 +161,45 @@ describe('Transaction Import', () => { cy.awaitLoading() cy.getInputFor('Account').type('My account{enter}') - cy.getInputFor('File').selectFile( - 'cypress/fixtures/transactions-parsed.csv' - ) + cy.getInputFor('File').selectFile('cypress/fixtures/comdirect.csv') cy.clickAndWait('Submit') cy.contains('This is a duplicate of an existing transaction') }) + + it('errors on already parsed file', function () { + cy.get('nav').contains('Transactions').click() + cy.getByTitle('Import Transactions').click() + cy.awaitLoading() + + cy.getInputFor('Account').type('account') + cy.contains('External Account').should('not.exist') + cy.contains('My Other Account') + cy.contains('My Account').click() + + cy.getInputFor('File').selectFile('cypress/fixtures/comdirect-ynap.csv') + + cy.contains('Submit').click() + cy.contains( + 'This file has already been parsed. Please upload the CSV file from your bank.' + ) + }) + + it('errors on an unparseable file', function () { + cy.get('nav').contains('Transactions').click() + cy.getByTitle('Import Transactions').click() + cy.awaitLoading() + + cy.getInputFor('Account').type('account') + cy.contains('External Account').should('not.exist') + cy.contains('My Other Account') + cy.contains('My Account').click() + + cy.getInputFor('File').selectFile( + 'cypress/fixtures/transactions-no-parser.csv' + ) + + cy.contains('Submit').click() + cy.contains('This file can not be parsed.') + }) }) diff --git a/cypress/fixtures/transactions-parsed.csv b/cypress/fixtures/comdirect-ynap.csv similarity index 80% rename from cypress/fixtures/transactions-parsed.csv rename to cypress/fixtures/comdirect-ynap.csv index bb355894..30877bc0 100644 --- a/cypress/fixtures/transactions-parsed.csv +++ b/cypress/fixtures/comdirect-ynap.csv @@ -2,4 +2,4 @@ Date,Payee,Memo,Outflow,Inflow 06/20/2023,Non Existing Account,Text,84.76, 06/19/2023,My Other Account,Transfer from my other account,,5.00 06/16/2023,New Account,REDACTED,784.94, -06/15/2023,External Account,Text,52.03, +06/15/2023,External Account,Text,52.03, \ No newline at end of file diff --git a/cypress/fixtures/comdirect.csv b/cypress/fixtures/comdirect.csv new file mode 100644 index 00000000..6d291a3a --- /dev/null +++ b/cypress/fixtures/comdirect.csv @@ -0,0 +1,11 @@ +; +"Umsätze Verrechnungskonto ";"Zeitraum: 30 Tage"; +"Neuer Kontostand";"16,94 EUR"; + +"Buchungstag";"Wertstellung (Valuta)";"Vorgang";"Buchungstext";"Umsatz in EUR"; +"20.06.2023";"20.06.2023";"Irrevevant";"Empfänger: Non Existing Account Buchungstext: Text Ref. 000000000/0 ";"-84,76"; +"19.06.2023";"19.06.2023";"Irrevevant";"Auftraggeber: My Other Account Buchungstext: Transfer from my other account Ref. 0000000000000/0 ";"5,00"; +"16.06.2023";"16.06.2023";"Irrevevant";"Empfänger: New Account Buchungstext: REDACTED Ref. 000000000/0 ";"-784,94"; +"15.06.2023";"15.06.2023";"Irrevevant";"Auftraggeber: External Account Buchungstext: Text Ref. 000000000/0 ";"-52,03"; + +"Alter Kontostand";"16,89 EUR"; diff --git a/cypress/fixtures/transactions-no-parser.csv b/cypress/fixtures/transactions-no-parser.csv new file mode 100644 index 00000000..30877bc0 --- /dev/null +++ b/cypress/fixtures/transactions-no-parser.csv @@ -0,0 +1,5 @@ +Date,Payee,Memo,Outflow,Inflow +06/20/2023,Non Existing Account,Text,84.76, +06/19/2023,My Other Account,Transfer from my other account,,5.00 +06/16/2023,New Account,REDACTED,784.94, +06/15/2023,External Account,Text,52.03, \ No newline at end of file diff --git a/docker-compose-production.yml b/docker-compose-production.yml index 46a36f4a..9f5efd06 100644 --- a/docker-compose-production.yml +++ b/docker-compose-production.yml @@ -1,7 +1,13 @@ version: '3' + +volumes: + ez-production-data: + services: backend: image: ghcr.io/envelope-zero/backend:v3.1.1 + volumes: + - ez-production-data:/data environment: API_URL: http://localhost:3001/api CORS_ALLOW_ORIGINS: http://localhost:3001 diff --git a/package-lock.json b/package-lock.json index 2f735fa9..e9eacceb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,9 @@ "packages": { "": { "name": "envelope-zero-frontend", + "hasInstallScript": true, "dependencies": { + "@envelope-zero/ynap-parsers": "1.15.13", "@headlessui/react": "1.7.17", "@heroicons/react": "2.0.18", "@tailwindcss/forms": "0.5.6", @@ -20,6 +22,7 @@ "@typescript-eslint/parser": "6.5.0", "@vitejs/plugin-react": "4.0.4", "autoprefixer": "10.4.15", + "buffer": "6.0.3", "cypress": "13.1.0", "eslint": "8.48.0", "eslint-config-prettier": "9.0.0", @@ -32,6 +35,7 @@ "http-proxy-middleware": "2.0.6", "i18next": "23.4.6", "js-big-decimal": "2.0.4", + "patch-package": "^8.0.0", "postcss": "8.4.29", "prettier": "3.0.3", "react": "18.2.0", @@ -40,6 +44,7 @@ "react-i18next": "13.2.2", "react-router-dom": "6.15.0", "sass": "1.66.1", + "stream-browserify": "3.0.0", "tailwindcss": "3.3.3", "typescript": "5.2.2", "vite": "4.4.9", @@ -805,7 +810,6 @@ "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", "engines": { "node": ">=6.9.0" }, @@ -2225,6 +2229,70 @@ "ms": "^2.1.1" } }, + "node_modules/@envelope-zero/ynap-parsers": { + "version": "1.15.13", + "resolved": "https://npm.pkg.github.com/download/@envelope-zero/ynap-parsers/1.15.13/1cbb8337dab8d01d120ef9ffff0361f8be26dd1f", + "integrity": "sha512-QsosiCmBms2zX18+6w8p7ryPiJ1+eTEGTCILZV/fZ6xmH0RC7R2Sjq1qLlZfXPacAc0Q9iPrfCV88BOgBLxWsQ==", + "license": "MIT", + "dependencies": { + "buffer": "6.0.3", + "date-fns": "2.30.0", + "iban": "0.0.14", + "iconv-lite": "0.6.3", + "jschardet": "3.0.0", + "lodash": "4.17.21", + "mdn-polyfills": "5.20.0", + "mt940-js": "1.0.0", + "papaparse": "5.4.1", + "slugify": "1.6.6", + "xlsx": "^0.18.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", @@ -2240,6 +2308,276 @@ "node": ">=12" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2761,6 +3099,31 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.2.tgz", + "integrity": "sha512-/PvU3MjSLVKJMRHsL6GW+wHQ9RaJuMW6fnn56sXNy+kP1L8nI/SHk9F9giIA+dnfzjKNJAulRjOR/fi9kzGuNA==", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@svgr/hast-util-to-babel-ast": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-7.0.0.tgz", @@ -3412,6 +3775,11 @@ "vite": "^4.2.0" } }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -3431,6 +3799,14 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -3833,29 +4209,6 @@ "npm": ">=6" } }, - "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/babel-plugin-macros/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", @@ -4030,9 +4383,9 @@ } }, "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -4049,7 +4402,7 @@ ], "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "ieee754": "^1.2.1" } }, "node_modules/buffer-crc32": { @@ -4131,6 +4484,18 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4263,6 +4628,14 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4344,20 +4717,29 @@ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "node_modules/cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "dependencies": { + "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", "parse-json": "^5.0.0", - "path-type": "^4.0.0" + "path-type": "^4.0.0", + "yaml": "^1.10.0" }, "engines": { - "node": ">=14" + "node": ">=10" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" + "engines": { + "node": ">=0.8" } }, "node_modules/cross-spawn": { @@ -4452,9 +4834,32 @@ } }, "node_modules/cypress/node_modules/@types/node": { - "version": "16.18.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.47.tgz", - "integrity": "sha512-yBaT6qZKmvaeTuv8kfv2QwIsgi/D4bYSLmHow/IBxjLNRHxYEXgwVRvBmnNLBXi3CkZg0Wdzu3NTUlUjjxconQ==" + "version": "16.18.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.48.tgz", + "integrity": "sha512-mlaecDKQ7rIZrYD7iiKNdzFb6e/qD5I9U1rAhq+Fd+DWvYVs+G2kv74UFHmSOlg5+i/vF3XxuR522V4u8BqO+Q==" + }, + "node_modules/cypress/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } }, "node_modules/cypress/node_modules/supports-color": { "version": "8.1.1", @@ -4486,6 +4891,21 @@ "node": ">=0.10" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/dayjs": { "version": "1.11.9", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", @@ -5856,6 +6276,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat-cache": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", @@ -5948,6 +6376,14 @@ "node": ">= 0.12" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fraction.js": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", @@ -6361,6 +6797,22 @@ "@babel/runtime": "^7.22.5" } }, + "node_modules/iban": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/iban/-/iban-0.0.14.tgz", + "integrity": "sha512-+rocNKk+Ga9m8Lr9fTMWd+87JnsBrucm0ZsIx5ROOarZlaDLmd+FKdbtvb0XyoBw9GAFOYG2GuLqoNB16d+p3w==" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/idb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", @@ -6595,6 +7047,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6856,6 +7322,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -7074,6 +7551,14 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, + "node_modules/jschardet": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-3.0.0.tgz", + "integrity": "sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -7105,6 +7590,17 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "node_modules/json-stable-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz", + "integrity": "sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g==", + "dependencies": { + "jsonify": "^0.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -7137,6 +7633,14 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsprim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", @@ -7173,6 +7677,14 @@ "json-buffer": "3.0.1" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", @@ -7367,6 +7879,11 @@ "lz-string": "bin/bin.js" } }, + "node_modules/mdn-polyfills": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/mdn-polyfills/-/mdn-polyfills-5.20.0.tgz", + "integrity": "sha512-AbTv1ytcoOUAkxw6u5oo2QPf27kEZgxBAQr49jFb4i2VnTnFGfJbcIQ9UDBOdfNECeXsgkYFwB2BkdeTfOzztw==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -7459,6 +7976,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/mt940-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mt940-js/-/mt940-js-1.0.0.tgz", + "integrity": "sha512-9iFCkaGoUbo8FfkchMyMsUBxwmOVQbIQpgc7C0tHf6xDSzv+soVvx4PNNng/eQm/vFvCGl7ot4zJRK7Zs4veww==" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -7682,6 +8204,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -7698,6 +8235,14 @@ "node": ">= 0.8.0" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ospath": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", @@ -7745,6 +8290,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7773,6 +8323,73 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/patch-package/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/patch-package/node_modules/yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "engines": { + "node": ">= 14" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7941,6 +8558,14 @@ } } }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "engines": { + "node": ">= 14" + } + }, "node_modules/postcss-nested": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", @@ -8254,6 +8879,19 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8664,6 +9302,14 @@ "node": ">=8" } }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -8672,6 +9318,17 @@ "node": ">=0.10.0" } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -8726,6 +9383,23 @@ "node": ">= 0.4" } }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-natural-compare": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", @@ -9650,6 +10324,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/workbox-background-sync": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz", @@ -9772,17 +10462,37 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "engines": { - "node": ">= 14" + "node": ">= 6" } }, "node_modules/yauzl": { diff --git a/package.json b/package.json index 7840f435..bdbcd4a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "envelope-zero-frontend", "dependencies": { + "@envelope-zero/ynap-parsers": "1.15.13", "@headlessui/react": "1.7.17", "@heroicons/react": "2.0.18", "@tailwindcss/forms": "0.5.6", @@ -15,6 +16,7 @@ "@typescript-eslint/parser": "6.5.0", "@vitejs/plugin-react": "4.0.4", "autoprefixer": "10.4.15", + "buffer": "6.0.3", "cypress": "13.1.0", "eslint": "8.48.0", "eslint-config-prettier": "9.0.0", @@ -27,6 +29,7 @@ "http-proxy-middleware": "2.0.6", "i18next": "23.4.6", "js-big-decimal": "2.0.4", + "patch-package": "^8.0.0", "postcss": "8.4.29", "prettier": "3.0.3", "react": "18.2.0", @@ -35,6 +38,7 @@ "react-i18next": "13.2.2", "react-router-dom": "6.15.0", "sass": "1.66.1", + "stream-browserify": "3.0.0", "tailwindcss": "3.3.3", "typescript": "5.2.2", "vite": "4.4.9", @@ -66,7 +70,8 @@ "server:production": "docker-compose --project-name frontend-production --file docker-compose-production.yml up --build", "test": "cypress run --browser chromium", "test:watch": "cypress open", - "format": "prettier --write ." + "format": "prettier --write .", + "postinstall": "patch-package" }, "browserslist": { "production": [ diff --git a/patches/jschardet+3.0.0.patch b/patches/jschardet+3.0.0.patch new file mode 100644 index 00000000..17fc80b7 --- /dev/null +++ b/patches/jschardet+3.0.0.patch @@ -0,0 +1,88 @@ +diff --git a/node_modules/jschardet/src/eucjpprober.js b/node_modules/jschardet/src/eucjpprober.js +index e978878..a299685 100644 +--- a/node_modules/jschardet/src/eucjpprober.js ++++ b/node_modules/jschardet/src/eucjpprober.js +@@ -70,7 +70,11 @@ function EUCJPProber() { + } else if( codingState == constants.start ) { + var charLen = this._mCodingSM.getCurrentCharLen(); + if( i == 0 ) { +- this._mLastChar[1] = aBuf[0]; ++ try { ++ this._mLastChar[1] = aBuf[0]; ++ } catch(error) { ++ console.log(`error in jschardet: ${error}`) ++ } + this._mContextAnalyzer.feed(this._mLastChar, charLen); + this._mDistributionAnalyzer.feed(this._mLastChar, charLen); + } else { +@@ -80,7 +84,11 @@ function EUCJPProber() { + } + } + +- this._mLastChar[0] = aBuf[aLen - 1]; ++ try { ++ this._mLastChar[0] = aBuf[aLen - 1]; ++ } catch(error) { ++ console.log(`error in jschardet: ${error}`) ++ } + + if( this.getState() == constants.detecting ) { + if( this._mContextAnalyzer.gotEnoughData() && +diff --git a/node_modules/jschardet/src/mbcharsetprober.js b/node_modules/jschardet/src/mbcharsetprober.js +index c03719b..5390e06 100644 +--- a/node_modules/jschardet/src/mbcharsetprober.js ++++ b/node_modules/jschardet/src/mbcharsetprober.js +@@ -72,7 +72,11 @@ var logger = require('./logger'); + } else if( codingState == constants.start ) { + var charLen = this._mCodingSM.getCurrentCharLen(); + if( i == 0 ) { ++ try { + this._mLastChar[1] = aBuf[0]; ++ } catch(error) { ++ console.log(`error in jschardet: ${error}`) ++ } + this._mDistributionAnalyzer.feed(this._mLastChar, charLen); + } else { + this._mDistributionAnalyzer.feed(aBuf.slice(i-1,i+1), charLen); +@@ -80,7 +84,11 @@ var logger = require('./logger'); + } + } + +- this._mLastChar[0] = aBuf[aLen - 1]; ++ try { ++ this._mLastChar[0] = aBuf[aLen - 1]; ++ } catch(error) { ++ console.log(`error in jschardet: ${error}`) ++ } + + if( this.getState() == constants.detecting ) { + if( this._mDistributionAnalyzer.gotEnoughData() && +diff --git a/node_modules/jschardet/src/sjisprober.js b/node_modules/jschardet/src/sjisprober.js +index 3160a66..fde54cd 100644 +--- a/node_modules/jschardet/src/sjisprober.js ++++ b/node_modules/jschardet/src/sjisprober.js +@@ -70,7 +70,11 @@ function SJISProber() { + } else if( codingState == constants.start ) { + var charLen = this._mCodingSM.getCurrentCharLen(); + if( i == 0 ) { ++ try { + this._mLastChar[1] = aBuf[0]; ++ } catch(error) { ++ console.log(`error in jschardet: ${error}`) ++ } + this._mContextAnalyzer.feed(this._mLastChar.slice(2 - charLen), charLen); + this._mDistributionAnalyzer.feed(this._mLastChar, charLen); + } else { +@@ -80,7 +84,11 @@ function SJISProber() { + } + } + +- this._mLastChar[0] = aBuf[aLen - 1]; ++ try { ++ this._mLastChar[0] = aBuf[aLen - 1]; ++ } catch(error) { ++ console.log(`error in jschardet: ${error}`) ++ } + + if( this.getState() == constants.detecting ) { + if( this._mContextAnalyzer.gotEnoughData() && diff --git a/src/components/TransactionImport/Form.tsx b/src/components/TransactionImport/Form.tsx index 89090600..bc1af4c4 100644 --- a/src/components/TransactionImport/Form.tsx +++ b/src/components/TransactionImport/Form.tsx @@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next' import { useState } from 'react' import { Link } from 'react-router-dom' import { Account, TransactionPreview, Translation } from '../../types' +import { parseFile } from '@envelope-zero/ynap-parsers' import { checkStatus, parseJSON } from '../../lib/fetch-helper' import Error from '../Error' import FormFields from '../FormFields' @@ -29,23 +30,53 @@ const Form = ({ accounts, isLoading, setIsLoading, setResult }: Props) => { event.preventDefault() setIsLoading(true) + const file = (event.target as any).file.files[0] // TODO: without `any` - fetch(`/api/v1/import/ynab-import-preview?accountId=${accountId}`, { - method: 'POST', - body: new FormData(event.target as HTMLFormElement), - }) - .then(checkStatus) - .then(parseJSON) - .then(body => { - setIsLoading(false) - if (error) { - setError('') - } - setResult(body.data, accountId) + parseFile(file) + .then(result => { + // Get the data and transform it to FormData for the backend + const data = new FormData() + data.append( + 'file', + new File([new Blob([result[0].data])], 'ynap-transactions.csv', { + type: 'text/csv;charset=utf-8;', + }) + ) + + // Send the parsed file to the backend for processing + fetch(`/api/v1/import/ynab-import-preview?accountId=${accountId}`, { + method: 'POST', + body: data, + }) + .then(checkStatus) + .then(parseJSON) + .then(body => { + setIsLoading(false) + if (error) { + setError('') + } + setResult(body.data, accountId) + }) + .catch(error => { + setIsLoading(false) + setError(error.message) + }) }) .catch(error => { setIsLoading(false) - setError(error.message) + + // Set the appropriate error message + if ( + error.message === 'This file has already been converted by YNAP.' + ) { + setError(t('transactions.import.alreadyConverted')) + } else if ( + error.message === 'No parser is available for this file.' + ) { + setError(t('transactions.import.noParser')) + } else { + setError(error.message) + } }) }} > @@ -64,20 +95,12 @@ const Form = ({ accounts, isLoading, setIsLoading, setResult }: Props) => { ) : ( <> -

- {t('transactions.import.description')} -

-

{t('transactions.import.howTo.title')}

-
    - {['download', 'parse', 'upload'].map(step => ( -
  1. - ))} -
+

groups={[{ items: accounts }]} diff --git a/src/translations/en.json b/src/translations/en.json index c1595df0..381a5b3c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -88,20 +88,15 @@ "import": { "importTransactions": "Import Transactions", "account": "Account", - "description": "Import a list of transactions (for example a downloaded bank statement). \n\nYou will be able to review the parsed transactions before they are saved.", - "ynapCredits": "Uploads are parsed in your browser, powered by YNAP. A list of all currently supported banks can be found here.", + "description": "Import a list of transactions, for example a downloaded bank statement.\n\nYou will be able to review the transactions before they are saved.\n\nUploads are parsed in your browser, powered by YNAP, and by Envelope Zero. A list of all currently supported banks can be found here.", "previous": "Previous Transaction", "next": "Next Transaction", "importSuccess": "Successfully imported", "deleteSuccess": "Successfully deleted", "duplicateDetected": "This is a duplicate of an existing transaction", "complete": "Import complete", - "howTo": { - "title": "How To Import", - "download": "Download a .CSV export of your transactions from your bank", - "parse": "Parse that file using YNAP (A list of all currently supported banks can be found here)", - "upload": "Upload the parsed file here:" - } + "alreadyConverted": "This file has already been parsed. Please upload the CSV file from your bank.", + "noParser": "This file can not be parsed. Please check on https://l.envelope-zero.org/ynap-formats if your bank is supported." }, "from": "From", "to": "To", diff --git a/vite.config.ts b/vite.config.ts index 6db6e07f..cc792ead 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,6 +7,11 @@ import eslint from 'vite-plugin-eslint' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react(), eslint(), viteTsconfigPaths(), svgrPlugin()], + resolve: { + alias: { + stream: 'stream-browserify', // Needed so that charset detection for ynap-parsers works + }, + }, server: { open: false, port: 3000, From 8221cd1f25f4d49d8b0a1c199806c04e8d8b1cb2 Mon Sep 17 00:00:00 2001 From: Morre Date: Sun, 3 Sep 2023 12:01:38 +0200 Subject: [PATCH 2/3] fixup! feat: add YNAP parsing to transaction import --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 381a5b3c..39240cbf 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -96,7 +96,7 @@ "duplicateDetected": "This is a duplicate of an existing transaction", "complete": "Import complete", "alreadyConverted": "This file has already been parsed. Please upload the CSV file from your bank.", - "noParser": "This file can not be parsed. Please check on https://l.envelope-zero.org/ynap-formats if your bank is supported." + "noParser": "This file can not be parsed. Please check if your bank is on the list of supported formats linked below." }, "from": "From", "to": "To", From fcc48838fa17a5e8e4c01cfff053bb6dfe38539e Mon Sep 17 00:00:00 2001 From: morre Date: Sun, 3 Sep 2023 12:14:05 +0200 Subject: [PATCH 3/3] Update src/components/TransactionImport/Form.tsx Co-authored-by: Fynn Heintz --- src/components/TransactionImport/Form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TransactionImport/Form.tsx b/src/components/TransactionImport/Form.tsx index bc1af4c4..3e9cf984 100644 --- a/src/components/TransactionImport/Form.tsx +++ b/src/components/TransactionImport/Form.tsx @@ -38,7 +38,7 @@ const Form = ({ accounts, isLoading, setIsLoading, setResult }: Props) => { const data = new FormData() data.append( 'file', - new File([new Blob([result[0].data])], 'ynap-transactions.csv', { + new File([new Blob([result[0]?.data])], 'ynap-transactions.csv', { type: 'text/csv;charset=utf-8;', }) )