diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4f887aa14..cf0b061ef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -212,7 +212,7 @@ jobs: report_name: Coverage Frontend type: lcov result_path: ./coverage/lcov.info - min_coverage: 18 + min_coverage: 19 token: ${{ github.token }} #test: diff --git a/community_server/src/Controller/AppRequestsController.php b/community_server/src/Controller/AppRequestsController.php index 12ea77d0b..4d70a68b1 100644 --- a/community_server/src/Controller/AppRequestsController.php +++ b/community_server/src/Controller/AppRequestsController.php @@ -156,6 +156,9 @@ class AppRequestsController extends AppController if($required_fields !== true) { return $this->returnJson($required_fields); } + if(!isset($params['memo']) || strlen($params['memo']) < 5 || strlen($params['memo']) > 150) { + return $this->returnJson(['state' => 'error', 'msg' => 'memo is not set or not in expected range [5;150]']); + } $params['transaction_type'] = 'transfer'; $requestAnswear = $this->JsonRequestClient->sendRequest(json_encode($params), '/createTransaction'); diff --git a/community_server/src/Controller/TransactionSendCoinsController.php b/community_server/src/Controller/TransactionSendCoinsController.php index 033e2343f..1018309cc 100644 --- a/community_server/src/Controller/TransactionSendCoinsController.php +++ b/community_server/src/Controller/TransactionSendCoinsController.php @@ -237,6 +237,11 @@ class TransactionSendCoinsController extends AppController $this->set('timeUsed', microtime(true) - $startTime); return; } + if($answear_data['msg'] === 'memo is not set or not in expected range [5;150]') { + $this->Flash->error(__('Ein Verwendungszweck zwischen 5 und 150 Zeichen wird benötig!')); + $this->set('timeUsed', microtime(true) - $startTime); + return; + } } else if($answear_data['state'] === 'not found' && $answear_data['msg'] === 'receiver not found') { $this->Flash->error(__('Der Empfänger wurde nicht auf dem Login-Server gefunden, hat er sein Konto schon angelegt?')); $this->set('timeUsed', microtime(true) - $startTime); diff --git a/frontend/src/App.vue b/frontend/src/App.vue index d44d64ab3..a6613bec1 100755 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -49,40 +49,49 @@ a, .copyright { color: #5a7b02; } -gradido-global-color-text { +.font1_2em { + font-size: 1.2em; +} +.font2em { + font-size: 1.5em; +} +.gradido-global-color-text { color: #3d443b; } -gradido-global-color-accent { +.gradido-global-color-accent { color: #047006; } -gradido-global-color-6e0a9c9e { +.gradido-global-color-6e0a9c9e { color: #000; } -gradido-global-color-2d0fb154 { +.gradido-global-color-2d0fb154 { color: #047006; } -gradido-global-color-16efe88c { +.gradido-global-color-16efe88c { color: #7ebc55; } -gradido-global-color-1939326 { +.gradido-global-color-1939326 { color: #f6fff6; } -gradido-global-color-9d79fc1 { +.gradido-global-color-9d79fc1 { color: #047006; } -gradido-global-color-6347f4d { +.gradido-global-color-6347f4d { color: #5a7b02; } -gradido-global-color-4fbc19a { +.gradido-global-color-4fbc19a { color: #014034; } -gradido-global-color-d341874 { +.gradido-global-color-d341874 { color: #b6d939; } -gradido-global-color-619d338 { +.gradido-global-color-619d338 { color: #8ebfb1; } -gradido-global-color-44819a9 { +.gradido-global-color-44819a9 { color: #026873; } +.gradido-global-color-gray { + color: #858383; +} diff --git a/frontend/src/apis/loginAPI.js b/frontend/src/apis/loginAPI.js index f5b0c57f5..55e32e6dd 100644 --- a/frontend/src/apis/loginAPI.js +++ b/frontend/src/apis/loginAPI.js @@ -1,5 +1,7 @@ import axios from 'axios' import CONFIG from '../config' +// eslint-disable-next-line no-unused-vars +import regeneratorRuntime from 'regenerator-runtime' // control email-text sended with email verification code const EMAIL_TYPE = { @@ -86,6 +88,16 @@ const loginAPI = { } return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload) }, + updateLanguage: async (sessionId, email, language) => { + const payload = { + session_id: sessionId, + email, + update: { + 'User.language': language, + }, + } + return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload) + }, } export default loginAPI diff --git a/frontend/src/components/LanguageSwitch.spec.js b/frontend/src/components/LanguageSwitch.spec.js new file mode 100644 index 000000000..14dc978ac --- /dev/null +++ b/frontend/src/components/LanguageSwitch.spec.js @@ -0,0 +1,95 @@ +import { mount } from '@vue/test-utils' +import LanguageSwitch from './LanguageSwitch' + +const localVue = global.localVue + +describe('LanguageSwitch', () => { + let wrapper + + const state = { + sessionId: 1234, + email: 'he@ho.he', + language: null, + } + + const mocks = { + $store: { + state, + commit: jest.fn(), + }, + $i18n: { + locale: 'en', + }, + } + + const Wrapper = () => { + return mount(LanguageSwitch, { localVue, mocks }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the component', () => { + expect(wrapper.find('div.language-switch').exists()).toBeTruthy() + }) + + describe('with locales en and de', () => { + describe('empty store', () => { + it('shows English as default navigator langauge', () => { + expect(wrapper.find('button.dropdown-toggle').text()).toBe('English - en') + }) + + describe('navigator language is "de-DE"', () => { + const mockNavigator = jest.fn(() => { + return 'de' + }) + + it('shows Deutsch as language ', async () => { + wrapper.vm.getNavigatorLanguage = mockNavigator + wrapper.vm.setCurrentLanguage() + await wrapper.vm.$nextTick() + expect(wrapper.find('button.dropdown-toggle').text()).toBe('Deutsch - de') + }) + }) + + describe('navigator language is "es-ES" (not supported)', () => { + const mockNavigator = jest.fn(() => { + return 'es' + }) + + it('shows English as language ', async () => { + wrapper.vm.getNavigatorLanguage = mockNavigator + wrapper.vm.setCurrentLanguage() + await wrapper.vm.$nextTick() + expect(wrapper.find('button.dropdown-toggle').text()).toBe('English - en') + }) + }) + }) + + describe('language "de" in store', () => { + it('shows Deutsch as language', async () => { + wrapper.vm.$store.state.language = 'de' + wrapper.vm.setCurrentLanguage() + await wrapper.vm.$nextTick() + expect(wrapper.find('button.dropdown-toggle').text()).toBe('Deutsch - de') + }) + }) + + describe('dropdown menu', () => { + it('has English and German as languages to choose', () => { + expect(wrapper.findAll('li')).toHaveLength(2) + }) + + it('has English as first language to choose', () => { + expect(wrapper.findAll('li').at(0).text()).toBe('English') + }) + + it('has German as second language to choose', () => { + expect(wrapper.findAll('li').at(1).text()).toBe('Deutsch') + }) + }) + }) + }) +}) diff --git a/frontend/src/components/LanguageSwitch.vue b/frontend/src/components/LanguageSwitch.vue index 0fad036a6..2b160b3e3 100644 --- a/frontend/src/components/LanguageSwitch.vue +++ b/frontend/src/components/LanguageSwitch.vue @@ -1,22 +1,72 @@ diff --git a/frontend/src/components/SidebarPlugin/SideBar.spec.js b/frontend/src/components/SidebarPlugin/SideBar.spec.js index 36230849f..9f9ce8a8f 100644 --- a/frontend/src/components/SidebarPlugin/SideBar.spec.js +++ b/frontend/src/components/SidebarPlugin/SideBar.spec.js @@ -19,6 +19,7 @@ describe('SideBar', () => { state: { email: 'test@example.org', }, + commit: jest.fn(), }, $i18n: { locale: 'en', diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 9719aab20..3781e87d0 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -18,6 +18,7 @@ "de": "Deutsch", "en": "English" }, + "decay": "Vergänglichkeit", "form": { "cancel": "Abbrechen", "reset": "Zurücksetzen", @@ -93,7 +94,9 @@ }, "thx": { "title": "Danke!", - "subtitle": "Wir haben dir eine eMail gesendet." + "email": "Wir haben dir eine eMail gesendet.", + "reset": "Dein Passwort wurde geändert.", + "register": "Du bist jetzt regisriert." }, "overview":{ "account_overview":"Kontoübersicht", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 72a2fce2e..aa42d966e 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -18,6 +18,7 @@ "de": "Deutsch", "en": "English" }, + "decay": "Decay", "form": { "cancel":"Cancel", "reset": "Reset", @@ -93,7 +94,9 @@ }, "thx": { "title": "Thank you!", - "subtitle": "We have sent you an email." + "email": "We have sent you an email.", + "reset": "Your password has been changed.", + "register": "You are registred now." }, "overview":{ "account_overview":"Account overview", diff --git a/frontend/src/locales/index.js b/frontend/src/locales/index.js new file mode 100644 index 000000000..4cb375b40 --- /dev/null +++ b/frontend/src/locales/index.js @@ -0,0 +1,16 @@ +const locales = [ + { + name: 'English', + code: 'en', + iso: 'en-US', + enabled: true, + }, + { + name: 'Deutsch', + code: 'de', + iso: 'de-DE', + enabled: true, + }, +] + +export default locales diff --git a/frontend/src/routes/routes.js b/frontend/src/routes/routes.js index 2ba1e6b68..44ef1f27a 100755 --- a/frontend/src/routes/routes.js +++ b/frontend/src/routes/routes.js @@ -33,8 +33,16 @@ const routes = [ component: () => import('../views/Pages/Login.vue'), }, { - path: '/thx', + path: '/thx/:comingFrom', component: () => import('../views/Pages/thx.vue'), + beforeEnter: (to, from, next) => { + const validFrom = ['password', 'reset', 'register'] + if (!validFrom.includes(from.path.split('/')[1])) { + next({ path: '/login' }) + } else { + next() + } + }, }, { path: '/password', diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index aed286bc6..1d89894fb 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -18,7 +18,8 @@ export const mutations = { export const actions = { login: ({ dispatch, commit }, data) => { commit('sessionId', data.sessionId) - commit('email', data.email) + commit('email', data.user.email) + commit('language', data.user.language) }, logout: ({ commit, state }) => { commit('sessionId', null) @@ -36,7 +37,7 @@ export const store = new Vuex.Store({ state: { sessionId: null, email: '', - language: 'en', + language: null, modals: false, }, getters: {}, diff --git a/frontend/src/store/store.test.js b/frontend/src/store/store.test.js index 655cca7a0..11dd5949b 100644 --- a/frontend/src/store/store.test.js +++ b/frontend/src/store/store.test.js @@ -35,20 +35,37 @@ describe('Vuex store', () => { const commit = jest.fn() const state = {} - it('calls two commits', () => { - login({ commit, state }, { sessionId: 1234, email: 'someone@there.is' }) - expect(commit).toHaveBeenCalledTimes(2) + it('calls three commits', () => { + login( + { commit, state }, + { sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } }, + ) + expect(commit).toHaveBeenCalledTimes(3) }) it('commits sessionId', () => { - login({ commit, state }, { sessionId: 1234, email: 'someone@there.is' }) + login( + { commit, state }, + { sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } }, + ) expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', 1234) }) it('commits email', () => { - login({ commit, state }, { sessionId: 1234, email: 'someone@there.is' }) + login( + { commit, state }, + { sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } }, + ) expect(commit).toHaveBeenNthCalledWith(2, 'email', 'someone@there.is') }) + + it('commits language', () => { + login( + { commit, state }, + { sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } }, + ) + expect(commit).toHaveBeenNthCalledWith(3, 'language', 'en') + }) }) describe('logout', () => { diff --git a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js index 40b1beac8..a80c11ad7 100644 --- a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js +++ b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js @@ -44,6 +44,9 @@ describe('DashboardLayoutGdd', () => { const store = new Vuex.Store({ state, + mutations: { + language: jest.fn(), + }, }) const Wrapper = () => { diff --git a/frontend/src/views/Pages/AccountOverview.spec.js b/frontend/src/views/Pages/AccountOverview.spec.js index 0ee75ef9f..031828129 100644 --- a/frontend/src/views/Pages/AccountOverview.spec.js +++ b/frontend/src/views/Pages/AccountOverview.spec.js @@ -28,7 +28,7 @@ describe('AccountOverview', () => { }) it('has a transactions table', () => { - expect(wrapper.find('gdd-table-stub').exists()).toBeTruthy() + expect(wrapper.find('gdd-transaction-list-stub').exists()).toBeTruthy() }) }) }) diff --git a/frontend/src/views/Pages/AccountOverview.vue b/frontend/src/views/Pages/AccountOverview.vue index 5064439f2..5914e4ef3 100644 --- a/frontend/src/views/Pages/AccountOverview.vue +++ b/frontend/src/views/Pages/AccountOverview.vue @@ -28,7 +28,7 @@
- - + diff --git a/frontend/src/views/Pages/AccountOverview/GddTableFooter.vue b/frontend/src/views/Pages/AccountOverview/GddTransactionListFooter.vue similarity index 91% rename from frontend/src/views/Pages/AccountOverview/GddTableFooter.vue rename to frontend/src/views/Pages/AccountOverview/GddTransactionListFooter.vue index 74f254ebb..0e30d85dc 100644 --- a/frontend/src/views/Pages/AccountOverview/GddTableFooter.vue +++ b/frontend/src/views/Pages/AccountOverview/GddTransactionListFooter.vue @@ -13,7 +13,7 @@ + diff --git a/frontend/src/views/Pages/UserProfileTransactionList.vue b/frontend/src/views/Pages/UserProfileTransactionList.vue index 1da6c2831..1cf6b6d33 100644 --- a/frontend/src/views/Pages/UserProfileTransactionList.vue +++ b/frontend/src/views/Pages/UserProfileTransactionList.vue @@ -3,7 +3,7 @@ - diff --git a/login_server/src/cpp/JSONInterface/JsonCreateTransaction.cpp b/login_server/src/cpp/JSONInterface/JsonCreateTransaction.cpp index 786af9d71..7fb40913e 100644 --- a/login_server/src/cpp/JSONInterface/JsonCreateTransaction.cpp +++ b/login_server/src/cpp/JSONInterface/JsonCreateTransaction.cpp @@ -108,6 +108,9 @@ Poco::JSON::Object* JsonCreateTransaction::transfer(Poco::Dynamic::Var params) else { result = stateError("parameter format unknown"); } + if (mMemo.size() < 5 || mMemo.size() > 150) { + result = stateError("memo is not set or not in expected range [5;150]"); + } if (result) { mm->releaseMemory(target_pubkey); return result; diff --git a/login_server/src/cpp/model/gradido/TransactionBase.h b/login_server/src/cpp/model/gradido/TransactionBase.h index 6a82b2a28..9f5a87d85 100644 --- a/login_server/src/cpp/model/gradido/TransactionBase.h +++ b/login_server/src/cpp/model/gradido/TransactionBase.h @@ -31,7 +31,8 @@ namespace model { TRANSACTION_VALID_INVALID_AMOUNT, TRANSACTION_VALID_INVALID_PUBKEY, TRANSACTION_VALID_INVALID_GROUP_ALIAS, - TRANSACTION_VALID_INVALID_SIGN + TRANSACTION_VALID_INVALID_SIGN, + TRANSACTION_VALID_INVALID_MEMO }; const char* TransactionValidationToString(TransactionValidation result); diff --git a/login_server/src/cpp/model/gradido/TransactionTransfer.cpp b/login_server/src/cpp/model/gradido/TransactionTransfer.cpp index 759d79690..ef0d3a3f6 100644 --- a/login_server/src/cpp/model/gradido/TransactionTransfer.cpp +++ b/login_server/src/cpp/model/gradido/TransactionTransfer.cpp @@ -186,6 +186,10 @@ namespace model { addError(new Error(function_name, "sender and receiver are the same")); return TRANSACTION_VALID_INVALID_PUBKEY; } + if (mMemo.size() < 5 || mMemo.size() > 150) { + addError(new Error(function_name, "memo is not set or not in expected range [5;150]")); + return TRANSACTION_VALID_INVALID_MEMO; + } return TRANSACTION_VALID_OK; }