diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index ed9528b48..0ff43486c 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -34,6 +34,9 @@ import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualT import Decimal from 'decimal.js-light' import { calculateDecay } from '@/util/decay' +const MEMO_MAX_CHARS = 255 +const MEMO_MIN_CHARS = 5 + export const executeTransaction = async ( amount: Decimal, memo: string, @@ -45,6 +48,14 @@ export const executeTransaction = async ( throw new Error('Sender and Recipient are the same.') } + if (memo.length > MEMO_MAX_CHARS) { + throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) + } + + if (memo.length < MEMO_MIN_CHARS) { + throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) + } + // validate amount const receivedCallDate = new Date() const sendBalance = await calculateBalance(sender.id, amount.mul(-1), receivedCallDate) diff --git a/frontend/src/components/GddSend/TransactionForm.spec.js b/frontend/src/components/GddSend/TransactionForm.spec.js index e105e2e1b..db971a2bc 100644 --- a/frontend/src/components/GddSend/TransactionForm.spec.js +++ b/frontend/src/components/GddSend/TransactionForm.spec.js @@ -62,11 +62,8 @@ describe('TransactionForm', () => { }) }) - describe('is selected: "send"', () => { + describe('send GDD', () => { beforeEach(async () => { - // await wrapper.setData({ - // selected: 'send', - // }) await wrapper.findAll('input[type="radio"]').at(0).setChecked() }) @@ -78,15 +75,18 @@ describe('TransactionForm', () => { beforeEach(() => { wrapper.setProps({ balance: 100.0 }) }) + describe('transaction form show because balance 100,0 GDD', () => { it('has no warning message ', () => { expect(wrapper.find('.errors').exists()).toBe(false) }) + it('has a reset button', () => { expect(wrapper.find('.test-buttons').findAll('button').at(0).attributes('type')).toBe( 'reset', ) }) + it('has a submit button', () => { expect(wrapper.find('.test-buttons').findAll('button').at(1).attributes('type')).toBe( 'submit', @@ -121,6 +121,12 @@ describe('TransactionForm', () => { expect(wrapper.find('span.errors').text()).toBe('validations.messages.email') }) + it('flushes an error message when email is the email of logged in user', async () => { + await wrapper.find('#input-group-1').find('input').setValue('user@example.org') + await flushPromises() + expect(wrapper.find('span.errors').text()).toBe('form.validation.is-not') + }) + it('trims the email after blur', async () => { await wrapper.find('#input-group-1').find('input').setValue(' valid@email.com ') await wrapper.find('#input-group-1').find('input').trigger('blur') @@ -195,6 +201,41 @@ describe('TransactionForm', () => { expect(wrapper.find('span.errors').text()).toBe('validations.messages.min') }) + it('flushes an error message when memo is more than 255 characters', async () => { + await wrapper.find('#input-group-3').find('textarea').setValue(` +Es ist ein König in Thule, der trinkt +Champagner, es geht ihm nichts drüber; +Und wenn er seinen Champagner trinkt, +Dann gehen die Augen ihm über. + +Die Ritter sitzen um ihn her, +Die ganze Historische Schule; +Ihm aber wird die Zunge schwer, +Es lallt der König von Thule: + +„Als Alexander, der Griechenheld, +Mit seinem kleinen Haufen +Erobert hatte die ganze Welt, +Da gab er sich ans Saufen. + +Ihn hatten so durstig gemacht der Krieg +Und die Schlachten, die er geschlagen; +Er soff sich zu Tode nach dem Sieg, +Er konnte nicht viel vertragen. + +Ich aber bin ein stärkerer Mann +Und habe mich klüger besonnen: +Wie jener endete, fang ich an, +Ich hab mit dem Trinken begonnen. + +Im Rausche wird der Heldenzug +Mir später weit besser gelingen; +Dann werde ich, taumelnd von Krug zu Krug, +Die ganze Welt bezwingen.“`) + await flushPromises() + expect(wrapper.find('span.errors').text()).toBe('validations.messages.max') + }) + it('flushes no error message when memo is valid', async () => { await wrapper.find('#input-group-3').find('textarea').setValue('Long enough') await flushPromises() diff --git a/frontend/src/components/GddSend/TransactionForm.vue b/frontend/src/components/GddSend/TransactionForm.vue index 4ffc0d3fc..5e683d132 100644 --- a/frontend/src/components/GddSend/TransactionForm.vue +++ b/frontend/src/components/GddSend/TransactionForm.vue @@ -99,7 +99,7 @@ :rules="{ required: true, min: 5, - max: 150, + max: 255, }" :name="$t('form.message')" v-slot="{ errors }" diff --git a/frontend/src/components/TransactionRows/AmountAndNameRow.vue b/frontend/src/components/TransactionRows/AmountAndNameRow.vue index be71b57f6..322ad7dfa 100644 --- a/frontend/src/components/TransactionRows/AmountAndNameRow.vue +++ b/frontend/src/components/TransactionRows/AmountAndNameRow.vue @@ -10,9 +10,20 @@
- - {{ itemText }} - +
+ + {{ itemText }} + + + {{ $t('via_link') }} + + +
{{ itemText }}
@@ -35,6 +46,11 @@ export default { type: String, required: false, }, + transactionLinkId: { + type: Number, + required: false, + default: null, + }, }, methods: { tunnelEmail() { diff --git a/frontend/src/components/Transactions/TransactionReceive.vue b/frontend/src/components/Transactions/TransactionReceive.vue index d1947a91a..f83c84e53 100644 --- a/frontend/src/components/Transactions/TransactionReceive.vue +++ b/frontend/src/components/Transactions/TransactionReceive.vue @@ -13,7 +13,12 @@ - + @@ -86,6 +91,10 @@ export default { type: Date, required: true, }, + transactionLinkId: { + type: Number, + required: false, + }, }, data() { return { diff --git a/frontend/src/components/Transactions/TransactionSend.vue b/frontend/src/components/Transactions/TransactionSend.vue index dfe50225a..2f9ef54d2 100644 --- a/frontend/src/components/Transactions/TransactionSend.vue +++ b/frontend/src/components/Transactions/TransactionSend.vue @@ -13,7 +13,12 @@ - + @@ -87,6 +92,10 @@ export default { type: Date, required: true, }, + transactionLinkId: { + type: Number, + required: false, + }, }, data() { return { diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 60b929c6d..e47da0fea 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -70,6 +70,7 @@ export const transactionsQuery = gql` linkedUser { email } + transactionLinkId } } } diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index adbde7a01..e8994a2e9 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -115,6 +115,7 @@ "redeem-text": "Willst du den Betrag jetzt einlösen?", "redeemed": "Erfolgreich eingelöst! Deinem Konto wurden {n} GDD gutgeschrieben.", "redeemed-at": "Der Link wurde bereits am {date} eingelöst.", + "redeemed-title": "eingelöst", "to-login": "Log dich ein", "to-register": "Registriere ein neues Konto", "valid_until": "Gültig bis" @@ -249,5 +250,6 @@ }, "transaction-link": { "send_you": "sendet dir" - } + }, + "via_link": "über einen Link" } diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 8d4665871..c586bbe46 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -115,6 +115,7 @@ "redeem-text": "Do you want to redeem the amount now?", "redeemed": "Successfully redeemed! Your account has been credited with {n} GDD.", "redeemed-at": "The link was already redeemed on {date}.", + "redeemed-title": "redeemed", "to-login": "Log in", "to-register": "Register a new account", "valid_until": "Valid until" @@ -249,5 +250,6 @@ }, "transaction-link": { "send_you": "wants to send you" - } + }, + "via_link": "via Link" }