diff --git a/backend/.env.dist b/backend/.env.dist index 8cef61eff..585bf0d94 100644 --- a/backend/.env.dist +++ b/backend/.env.dist @@ -22,4 +22,8 @@ DB_DATABASE=gradido_community #KLICKTIPP_PASSWORD= #KLICKTIPP_APIKEY_DE= #KLICKTIPP_APIKEY_EN= -#KLICKTIPP=true \ No newline at end of file +#KLICKTIPP=true +COMMUNITY_NAME= +COMMUNITY_URL= +COMMUNITY_REGISTER_URL= +COMMUNITY_DESCRIPTION= \ No newline at end of file diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index b2ba39553..f8e0bd3e9 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -30,6 +30,14 @@ const klicktipp = { KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN || 'SomeFakeKeyEN', } +const community = { + COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung', + COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/vue/', + COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/vue/register', + COMMUNITY_DESCRIPTION: + process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.', +} + const email = { EMAIL: process.env.EMAIL === 'true' || false, EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email', @@ -42,6 +50,6 @@ const email = { // This is needed by graphql-directive-auth process.env.APP_SECRET = server.JWT_SECRET -const CONFIG = { ...server, ...database, ...klicktipp, ...email } +const CONFIG = { ...server, ...database, ...klicktipp, ...community } export default CONFIG diff --git a/backend/src/graphql/model/Community.ts b/backend/src/graphql/model/Community.ts new file mode 100644 index 000000000..466028d00 --- /dev/null +++ b/backend/src/graphql/model/Community.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { ObjectType, Field } from 'type-graphql' + +@ObjectType() +export class Community { + constructor(json?: any) { + if (json) { + this.id = Number(json.id) + this.name = json.name + this.url = json.url + this.description = json.description + this.registerUrl = json.registerUrl + } + } + + @Field(() => Number) + id: number + + @Field(() => String) + name: string + + @Field(() => String) + url: string + + @Field(() => String) + description: string + + @Field(() => String) + registerUrl: string +} diff --git a/backend/src/graphql/resolver/CommunityResolver.ts b/backend/src/graphql/resolver/CommunityResolver.ts new file mode 100644 index 000000000..563c73d24 --- /dev/null +++ b/backend/src/graphql/resolver/CommunityResolver.ts @@ -0,0 +1,49 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +import { Resolver, Query } from 'type-graphql' +import CONFIG from '../../config' +import { Community } from '../model/Community' + +@Resolver() +export class CommunityResolver { + @Query(() => Community) + async getCommunityInfo(): Promise { + return new Community({ + name: CONFIG.COMMUNITY_NAME, + description: CONFIG.COMMUNITY_DESCRIPTION, + url: CONFIG.COMMUNITY_URL, + registerUrl: CONFIG.COMMUNITY_REGISTER_URL, + }) + } + + @Query(() => [Community]) + async communities(): Promise { + const communities: Community[] = [] + + communities.push( + new Community({ + id: 1, + name: 'Gradido Entwicklung', + description: 'Die lokale Entwicklungsumgebung von Gradido.', + url: 'http://localhost/vue/', + registerUrl: 'http://localhost/vue/register-community', + }), + new Community({ + id: 2, + name: 'Gradido Staging', + description: 'Der Testserver der Gradido-Akademie.', + url: 'https://stage1.gradido.net/vue/', + registerUrl: 'https://stage1.gradido.net/vue/register-community', + }), + new Community({ + id: 3, + name: 'Gradido-Akademie', + description: 'Freies Institut für Wirtschaftsbionik.', + url: 'https://gradido.net', + registerUrl: 'https://gdd1.gradido.com/vue/register-community', + }), + ) + return communities + } +} diff --git a/frontend/src/components/LanguageSwitchSelect.vue b/frontend/src/components/LanguageSwitchSelect.vue index 3467bdafd..545cef4e9 100644 --- a/frontend/src/components/LanguageSwitchSelect.vue +++ b/frontend/src/components/LanguageSwitchSelect.vue @@ -14,8 +14,8 @@ export default { return { selected: null, options: [ - { value: 'de', text: this.$t('languages.de') }, - { value: 'en', text: this.$t('languages.en') }, + { value: 'de', text: this.$t('settings.language.de') }, + { value: 'en', text: this.$t('settings.language.en') }, ], } }, diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index fe1c58e1b..3499a3fa1 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -105,3 +105,26 @@ export const checkEmailQuery = gql` } } ` + +export const communityInfo = gql` + query { + getCommunityInfo { + name + description + registerUrl + url + } + } +` + +export const communities = gql` + query { + communities { + id + name + url + description + registerUrl + } + } +` diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index baf86c747..4801d5e4b 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -1,14 +1,22 @@ { "back": "Zurück", - "community": "Gemeinschaft", - "communitys": { - "form": { - "date_period": "Datum / Zeitraum", - "hours": "Stunden", - "hours_report": "Stundenbericht", - "more_hours": "weitere Stunden", - "submit": "Einreichen" - } + "community": { + "choose-another-community": "Eine andere Gemeinschaft auswählen", + "communities": { + "form": { + "date_period": "Datum / Zeitraum", + "hours": "Stunden", + "hours_report": "Stundenbericht", + "more_hours": "weitere Stunden", + "submit": "Einreichen" + } + }, + "community": "Gemeinschaft", + "continue-to-registration": "Weiter zur Registrierung", + "current-community": "Aktuelle Gemeinschaft", + "location": "Ort:", + "other-communities": "Weitere Gemeinschaften", + "switch-to-this-community": "zu dieser Gemeinschaft wechseln" }, "decay": { "calculation_decay": "Berechnung der Vergänglichkeit", @@ -19,7 +27,6 @@ "decayStart": " - Startblock für Vergänglichkeit am: ", "decay_introduced": "Die Vergänglichkeit wurde Eingeführt am ", "decay_since_last_transaction": "Vergänglichkeit seit der letzten Transaktion", - "fromCommunity": "Aus der Gemeinschaft", "hours": "Stunden", "last_transaction": "Letzte Transaktion", "minutes": "Minuten", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index e0e0aa292..d3d5b05ab 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -1,14 +1,22 @@ { "back": "Back", - "community": "Community", - "communitys": { - "form": { - "date_period": "Date / Period", - "hours": "hours", - "hours_report": "Hourly report", - "more_hours": "more hours", - "submit": "submit" - } + "community": { + "choose-another-community": "Choose another community", + "communities": { + "form": { + "date_period": "Date / Period", + "hours": "hours", + "hours_report": "Hourly report", + "more_hours": "more hours", + "submit": "submit" + } + }, + "community": "Community", + "continue-to-registration": "Continue to registration", + "current-community": "Current community", + "location": "Location:", + "other-communities": "Other communities", + "switch-to-this-community": "switch to this community" }, "decay": { "calculation_decay": "Calculation of Decay", @@ -19,7 +27,6 @@ "decayStart": " - Starting block for decay at: ", "decay_introduced": "Decay was Introduced on", "decay_since_last_transaction": "Decay since the last transaction", - "fromCommunity": "From the community", "hours": "Hours", "last_transaction": "Last transaction:", "minutes": "Minutes", diff --git a/frontend/src/routes/routes.js b/frontend/src/routes/routes.js index 34d7a9d70..6bd1ac75d 100755 --- a/frontend/src/routes/routes.js +++ b/frontend/src/routes/routes.js @@ -36,7 +36,7 @@ const routes = [ path: '/thx/:comingFrom', component: () => import('../views/Pages/thx.vue'), beforeEnter: (to, from, next) => { - const validFrom = ['password', 'reset', 'register'] + const validFrom = ['password', 'reset', 'register', 'community'] if (!validFrom.includes(from.path.split('/')[1])) { next({ path: '/login' }) } else { @@ -48,6 +48,14 @@ const routes = [ path: '/password', component: () => import('../views/Pages/ForgotPassword.vue'), }, + { + path: '/register-community', + component: () => import('../views/Pages/RegisterCommunity.vue'), + }, + { + path: '/select-community', + component: () => import('../views/Pages/RegisterSelectCommunity.vue'), + }, { path: '/reset/:optin', component: () => import('../views/Pages/ResetPassword.vue'), diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index 5bb55c9d8..f9b40fd15 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -29,6 +29,9 @@ export const mutations = { newsletterState: (state, newsletterState) => { state.newsletterState = newsletterState }, + community: (state, community) => { + state.community = community + }, } export const actions = { @@ -69,6 +72,7 @@ export const store = new Vuex.Store({ token: null, coinanimation: true, newsletterState: null, + community: null, }, getters: {}, // Syncronous mutation of the state diff --git a/frontend/src/views/Pages/Login.spec.js b/frontend/src/views/Pages/Login.spec.js index 7218384f7..6aac3fda2 100644 --- a/frontend/src/views/Pages/Login.spec.js +++ b/frontend/src/views/Pages/Login.spec.js @@ -4,14 +4,20 @@ import Login from './Login' const localVue = global.localVue -const loginQueryMock = jest.fn().mockResolvedValue({ +const apolloQueryMock = jest.fn().mockResolvedValue({ data: { - login: 'token', + getCommunityInfo: { + name: 'test12', + description: 'test community 12', + url: 'http://test12.test12/', + registerUrl: 'http://test12.test12/vue/register', + }, }, }) const toastErrorMock = jest.fn() const mockStoreDispach = jest.fn() +const mockStoreCommit = jest.fn() const mockRouterPush = jest.fn() const spinnerHideMock = jest.fn() const spinnerMock = jest.fn(() => { @@ -30,6 +36,15 @@ describe('Login', () => { $t: jest.fn((t) => t), $store: { dispatch: mockStoreDispach, + commit: mockStoreCommit, + state: { + community: { + name: 'Gradido Entwicklung', + url: 'http://localhost/vue/', + registerUrl: 'http://localhost/vue/register', + description: 'Die lokale Entwicklungsumgebung von Gradido.', + }, + }, }, $loading: { show: spinnerMock, @@ -41,7 +56,7 @@ describe('Login', () => { error: toastErrorMock, }, $apollo: { - query: loginQueryMock, + query: apolloQueryMock, }, } @@ -62,20 +77,54 @@ describe('Login', () => { expect(wrapper.find('div.login-form').exists()).toBeTruthy() }) + it('commits the community info to the store', () => { + expect(mockStoreCommit).toBeCalledWith('community', { + name: 'test12', + description: 'test community 12', + url: 'http://test12.test12/', + registerUrl: 'http://test12.test12/vue/register', + }) + }) + + describe('communities gives back error', () => { + beforeEach(() => { + apolloQueryMock.mockRejectedValue({ + message: 'Failed to get communities', + }) + wrapper = Wrapper() + }) + + it('toasts an error message', () => { + expect(toastErrorMock).toBeCalledWith('Failed to get communities') + }) + }) + describe('Login header', () => { it('has a welcome message', () => { expect(wrapper.find('div.header').text()).toBe('Gradido site.login.community') }) }) + describe('Community Data', () => { + it('has a Community name', () => { + expect(wrapper.find('.test-communitydata b').text()).toBe('Gradido Entwicklung') + }) + + it('has a Community description', () => { + expect(wrapper.find('.test-communitydata p').text()).toBe( + 'Die lokale Entwicklungsumgebung von Gradido.', + ) + }) + }) + describe('links', () => { - it('has a link "Forgot Password?"', () => { + it('has a link "Forgot Password"', () => { expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual( 'settings.password.forgot_pwd', ) }) - it('links to /password when clicking "Forgot Password?"', () => { + it('links to /password when clicking "Forgot Password"', () => { expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/password') }) @@ -86,7 +135,9 @@ describe('Login', () => { }) it('links to /register when clicking "Create new account"', () => { - expect(wrapper.findAllComponents(RouterLinkStub).at(1).props().to).toBe('/register') + expect(wrapper.findAllComponents(RouterLinkStub).at(1).props().to).toBe( + '/register-community', + ) }) }) @@ -135,10 +186,15 @@ describe('Login', () => { await flushPromises() await wrapper.find('form').trigger('submit') await flushPromises() + apolloQueryMock.mockResolvedValue({ + data: { + login: 'token', + }, + }) }) it('calls the API with the given data', () => { - expect(loginQueryMock).toBeCalledWith( + expect(apolloQueryMock).toBeCalledWith( expect.objectContaining({ variables: { email: 'user@example.org', @@ -168,7 +224,7 @@ describe('Login', () => { describe('login fails', () => { beforeEach(() => { - loginQueryMock.mockRejectedValue({ + apolloQueryMock.mockRejectedValue({ message: 'Ouch!', }) }) diff --git a/frontend/src/views/Pages/Login.vue b/frontend/src/views/Pages/Login.vue index f5c6f025d..db8789002 100755 --- a/frontend/src/views/Pages/Login.vue +++ b/frontend/src/views/Pages/Login.vue @@ -18,9 +18,14 @@ -
- {{ $t('login') }} +
+ {{ $store.state.community.name }} +

+ {{ $store.state.community.description }} +

+ {{ $t('login') }}
+ @@ -38,13 +43,17 @@ - - + + {{ $t('settings.password.forgot_pwd') }} - - + + {{ $t('site.login.new_wallet') }} @@ -58,7 +67,7 @@ import CONFIG from '../../config' import InputPassword from '../../components/Inputs/InputPassword' import InputEmail from '../../components/Inputs/InputEmail' -import { login } from '../../graphql/queries' +import { login, communityInfo } from '../../graphql/queries' export default { name: 'login', @@ -103,6 +112,21 @@ export default { this.$toasted.error(this.$t('error.no-account')) }) }, + async onCreated() { + this.$apollo + .query({ + query: communityInfo, + }) + .then((result) => { + this.$store.commit('community', result.data.getCommunityInfo) + }) + .catch((error) => { + this.$toasted.error(error.message) + }) + }, + }, + created() { + this.onCreated() }, } diff --git a/frontend/src/views/Pages/Register.spec.js b/frontend/src/views/Pages/Register.spec.js index f3f66fca9..388a96746 100644 --- a/frontend/src/views/Pages/Register.spec.js +++ b/frontend/src/views/Pages/Register.spec.js @@ -26,6 +26,12 @@ describe('Register', () => { state: { email: 'peter@lustig.de', language: 'en', + community: { + name: 'Gradido Entwicklung', + url: 'http://localhost/vue/', + registerUrl: 'http://localhost/vue/register', + description: 'Die lokale Entwicklungsumgebung von Gradido.', + }, }, }, } @@ -53,6 +59,18 @@ describe('Register', () => { }) }) + describe('Community Data', () => { + it('has a Community name?', () => { + expect(wrapper.find('.test-communitydata b').text()).toBe('Gradido Entwicklung') + }) + + it('has a Community description?', () => { + expect(wrapper.find('.test-communitydata p').text()).toBe( + 'Die lokale Entwicklungsumgebung von Gradido.', + ) + }) + }) + describe('links', () => { it('has a link "Back"', () => { expect(wrapper.find('.test-button-back').text()).toEqual('back') @@ -127,6 +145,18 @@ describe('Register', () => { }) }) + describe('link Choose another community', () => { + it('has a link "Choose another community"', () => { + expect(wrapper.find('.test-button-another-community').text()).toEqual( + 'community.choose-another-community', + ) + }) + + it('links to /select-community when clicking "Choose another community"', () => { + expect(wrapper.find('.test-button-another-community').props().to).toBe('/select-community') + }) + }) + describe('API calls', () => { beforeEach(() => { wrapper.find('#registerFirstname').setValue('Max') diff --git a/frontend/src/views/Pages/Register.vue b/frontend/src/views/Pages/Register.vue index f08ea286e..85fe51325 100755 --- a/frontend/src/views/Pages/Register.vue +++ b/frontend/src/views/Pages/Register.vue @@ -13,15 +13,21 @@
+ + -
- {{ $t('signup') }} +
+ {{ $store.state.community.name }} +

+ {{ $store.state.community.description }} +

+
{{ $t('signup') }}
@@ -118,7 +124,7 @@
- + {{ $t('back') }} @@ -138,6 +144,15 @@ +
+ + {{ $t('community.choose-another-community') }} + +
+ diff --git a/frontend/src/views/Pages/RegisterSelectCommunity.spec.js b/frontend/src/views/Pages/RegisterSelectCommunity.spec.js new file mode 100644 index 000000000..396eae59b --- /dev/null +++ b/frontend/src/views/Pages/RegisterSelectCommunity.spec.js @@ -0,0 +1,81 @@ +import { mount } from '@vue/test-utils' +import RegisterSelectCommunity from './RegisterSelectCommunity' + +const localVue = global.localVue + +const spinnerHideMock = jest.fn() +const spinnerMock = jest.fn(() => { + return { + hide: spinnerHideMock, + } +}) +const apolloQueryMock = jest.fn().mockResolvedValue({ + data: { + communities: [ + { + name: 'test1', + description: 'description 1', + url: 'http://test.test/vue', + registerUrl: 'http://localhost/vue/register-community', + }, + ], + }, +}) +const toasterMock = jest.fn() + +describe('RegisterSelectCommunity', () => { + let wrapper + + const mocks = { + $i18n: { + locale: 'en', + }, + $t: jest.fn((t) => t), + $store: { + state: { + community: { + name: 'Gradido Entwicklung', + url: 'http://localhost/vue/', + registerUrl: 'http://localhost/vue/register', + description: 'Die lokale Entwicklungsumgebung von Gradido.', + }, + }, + }, + $apollo: { + query: apolloQueryMock, + }, + $loading: { + show: spinnerMock, + }, + $toasted: { + error: toasterMock, + }, + } + + const Wrapper = () => { + return mount(RegisterSelectCommunity, { localVue, mocks }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the Div Element "#register-select-community"', () => { + expect(wrapper.find('div#register-select-community').exists()).toBeTruthy() + }) + + describe('calls the apollo query', () => { + beforeEach(() => { + apolloQueryMock.mockRejectedValue({ + message: 'Wrong thing', + }) + wrapper = Wrapper() + }) + + it('toast an error', () => { + expect(toasterMock).toBeCalledWith('Wrong thing') + }) + }) + }) +}) diff --git a/frontend/src/views/Pages/RegisterSelectCommunity.vue b/frontend/src/views/Pages/RegisterSelectCommunity.vue new file mode 100644 index 000000000..1c4c5b990 --- /dev/null +++ b/frontend/src/views/Pages/RegisterSelectCommunity.vue @@ -0,0 +1,104 @@ + + + diff --git a/frontend/src/views/Pages/UserProfile/UserCard.spec.js b/frontend/src/views/Pages/UserProfile/UserCard.spec.js new file mode 100644 index 000000000..0fe13c416 --- /dev/null +++ b/frontend/src/views/Pages/UserProfile/UserCard.spec.js @@ -0,0 +1,36 @@ +import { mount } from '@vue/test-utils' +import UserCard from './UserCard' + +const localVue = global.localVue + +describe('UserCard', () => { + let wrapper + + const mocks = { + $t: jest.fn((t) => t), + $n: jest.fn((n) => String(n)), + $store: { + state: { + email: 'user@example.org', + }, + }, + } + + const Wrapper = () => { + return mount(UserCard, { localVue, mocks }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the Div Element ".userdata-card"', () => { + expect(wrapper.find('div.userdata-card').exists()).toBeTruthy() + }) + + it('renders the Div Element "vue-qrcode"', () => { + expect(wrapper.find('vue-qrcode')) + }) + }) +}) diff --git a/frontend/src/views/Pages/UserProfile/UserCard.vue b/frontend/src/views/Pages/UserProfile/UserCard.vue index 1610eb7e9..4ddbb2726 100755 --- a/frontend/src/views/Pages/UserProfile/UserCard.vue +++ b/frontend/src/views/Pages/UserProfile/UserCard.vue @@ -1,30 +1,32 @@