diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 145fe8bef..6ca1a1e5f 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -14,6 +14,9 @@ env: jobs: pre-commit: runs-on: ubuntu-latest + permissions: + packages: read + steps: - uses: actions/checkout@v3.5.3 @@ -23,6 +26,9 @@ jobs: node-version: '16' 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,11 +36,13 @@ jobs: cypress: runs-on: ubuntu-latest + permissions: + packages: read # Current backend version as service container services: backend: - image: ghcr.io/envelope-zero/backend:v3.0.1 + image: ghcr.io/envelope-zero/backend:v2.10.1 env: CORS_ALLOW_ORIGINS: http://localhost:3001 API_URL: http://localhost:3001/api @@ -44,6 +52,9 @@ jobs: steps: - uses: actions/checkout@v3.5.3 + - name: set npm auth + run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" > ~/.npmrc + - uses: cypress-io/github-action@v5.8.3 env: PORT: 3001 @@ -106,9 +117,9 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@v4.1.1 with: - context: . push: ${{ steps.push_check.outputs.push }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | REACT_APP_VERSION=${{ github.ref_name }} + GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..13a79428e --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@envelope-zero:registry=https://npm.pkg.github.com diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a819e650..fd6dadada 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 da2a398ac..fb72855e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,13 @@ -FROM node:20.5.1-alpine AS builder +FROM node:20.3.1-alpine AS builder ENV NODE_ENV production WORKDIR /app # copy package.json first to avoid unnecessary npm install COPY package.json package-lock.json /app/ -RUN npm install --production + +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/e2e/transaction-import.cy.ts b/cypress/e2e/transaction-import.cy.ts index 66797f2fb..1025bcd15 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,9 +161,7 @@ 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') diff --git a/cypress/fixtures/comdirect.csv b/cypress/fixtures/comdirect.csv new file mode 100644 index 000000000..6d291a3a1 --- /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-parsed.csv b/cypress/fixtures/transactions-parsed.csv deleted file mode 100644 index bb3558940..000000000 --- a/cypress/fixtures/transactions-parsed.csv +++ /dev/null @@ -1,5 +0,0 @@ -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, diff --git a/package.json b/package.json index 4c3d4a0da..33780486b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "envelope-zero-frontend", "dependencies": { + "@envelope-zero/ynap-parsers": "1.15.11", "@headlessui/react": "1.7.16", "@heroicons/react": "2.0.18", "@tailwindcss/forms": "0.5.4", @@ -11,6 +12,7 @@ "@types/node": "18.17.5", "@types/react": "18.2.20", "@types/react-dom": "18.2.7", + "buffer": "6.0.3", "flowbite": "1.8.1", "flowbite-react": "0.5.0", "http-proxy-middleware": "2.0.6", @@ -50,10 +52,7 @@ "start": "react-scripts start", "build": "react-scripts build", "server:dev": "docker-compose --project-name frontend-dev --file docker-compose-dev.yml up", - "server:test": "docker-compose --project-name frontend-test --file docker-compose-test.yml up --detach", - "server:test:stop": "docker-compose --project-name frontend-test --file docker-compose-test.yml down", - "server:test:restart": "docker-compose --project-name frontend-test --file docker-compose-test.yml restart", - "server:test:logs": "docker-compose --project-name frontend-test --file docker-compose-test.yml logs --follow", + "server:test": "docker-compose --project-name frontend-test --file docker-compose-test.yml up", "test": "cypress run", "test:watch": "cypress open", "eject": "react-scripts eject", diff --git a/src/components/TransactionImport/Form.tsx b/src/components/TransactionImport/Form.tsx index 89090600b..f31239ebc 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' @@ -28,25 +29,38 @@ const Form = ({ accounts, isLoading, setIsLoading, setResult }: Props) => { onSubmit={event => { 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) - }) - .catch(error => { - setIsLoading(false) - setError(error.message) + 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;', + }) + ) + + 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 => setError(error)) }} >
@@ -64,20 +78,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 c1595df04..13d1918d9 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -88,20 +88,13 @@ "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:" - } + "complete": "Import complete" }, "from": "From", "to": "To",