Merge remote-tracking branch 'origin/master' into
1798-feature-gradidoid-1-adapt-and-migrate-database-schema
@ -26,5 +26,5 @@ module.exports = {
|
||||
testMatch: ['**/?(*.)+(spec|test).js?(x)'],
|
||||
// snapshotSerializers: ['jest-serializer-vue'],
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/(?!vee-validate/dist/rules)'],
|
||||
// testEnvironment: 'jest-environment-jsdom-sixteen', // not needed anymore since jest@26, see: https://www.npmjs.com/package/jest-environment-jsdom-sixteen
|
||||
testEnvironment: 'jest-environment-jsdom-sixteen', // why this is still needed? should not be needed anymore since jest@26, see: https://www.npmjs.com/package/jest-environment-jsdom-sixteen
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "26.6.3",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-environment-jsdom-sixteen": "^2.0.0",
|
||||
"portal-vue": "^2.1.7",
|
||||
"qrcanvas-vue": "2.1.1",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
|
||||
39
admin/src/components/CommunityStatistic.spec.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import CommunityStatistic from './CommunityStatistic'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$n: jest.fn((n) => n),
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
value: {
|
||||
totalUsers: '123',
|
||||
activeUsers: '100',
|
||||
deletedUsers: '5',
|
||||
totalGradidoCreated: '2500',
|
||||
totalGradidoDecayed: '200',
|
||||
totalGradidoAvailable: '500',
|
||||
totalGradidoUnbookedDecayed: '111',
|
||||
},
|
||||
}
|
||||
|
||||
describe('CommunityStatistic', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(CommunityStatistic, { localVue, mocks, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the Div Element ".community-statistic"', () => {
|
||||
expect(wrapper.find('div.community-statistic').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
59
admin/src/components/CommunityStatistic.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="community-statistic">
|
||||
<div>
|
||||
<b-jumbotron bg-variant="info" text-variant="white" border-variant="dark">
|
||||
<template #header>{{ $t('statistic.name') }}</template>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<div>
|
||||
{{ $t('statistic.totalUsers') }}{{ $t('math.colon') }}
|
||||
<b>{{ value.totalUsers }}</b>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.activeUsers') }}{{ $t('math.colon') }}
|
||||
<b>{{ value.activeUsers }}</b>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.deletedUsers') }}{{ $t('math.colon') }}
|
||||
<b>{{ value.deletedUsers }}</b>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.totalGradidoCreated') }}{{ $t('math.colon') }}
|
||||
<b>{{ $n(value.totalGradidoCreated, 'decimal') }} {{ $t('GDD') }}</b>
|
||||
<small class="ml-5">{{ value.totalGradidoCreated }}</small>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.totalGradidoDecayed') }}{{ $t('math.colon') }}
|
||||
<b>{{ $n(value.totalGradidoDecayed, 'decimal') }} {{ $t('GDD') }}</b>
|
||||
<small class="ml-5">{{ value.totalGradidoDecayed }}</small>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.totalGradidoAvailable') }}{{ $t('math.colon') }}
|
||||
<b>{{ $n(value.totalGradidoAvailable, 'decimal') }} {{ $t('GDD') }}</b>
|
||||
<small class="ml-5">{{ value.totalGradidoAvailable }}</small>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.totalGradidoUnbookedDecayed') }}{{ $t('math.colon') }}
|
||||
<b>{{ $n(value.totalGradidoUnbookedDecayed, 'decimal') }} {{ $t('GDD') }}</b>
|
||||
<small class="ml-5">{{ value.totalGradidoUnbookedDecayed }}</small>
|
||||
</div>
|
||||
</b-jumbotron>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import CONFIG from '@/config'
|
||||
|
||||
export default {
|
||||
name: 'CommunityStatistic',
|
||||
props: {
|
||||
value: { type: Object },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
CONFIG,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
29
admin/src/components/ContentFooter.spec.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContentFooter from './ContentFooter'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$i18n: {
|
||||
locale: jest.fn(() => 'en'),
|
||||
},
|
||||
}
|
||||
|
||||
describe('ContentFooter', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(ContentFooter, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the div element ".content-footer"', () => {
|
||||
expect(wrapper.find('div.content-footer').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="content-footer">
|
||||
<hr />
|
||||
<b-row align-v="center" class="mt-4 justify-content-lg-between">
|
||||
<b-col>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionLinkForm from './ContributionLinkForm.vue'
|
||||
import { toastErrorSpy } from '../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -8,9 +9,13 @@ global.alert = jest.fn()
|
||||
const propsData = {
|
||||
contributionLinkData: {},
|
||||
}
|
||||
const apolloMutateMock = jest.fn().mockResolvedValue()
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$apollo: {
|
||||
mutate: apolloMutateMock,
|
||||
},
|
||||
}
|
||||
|
||||
// const mockAPIcall = jest.fn()
|
||||
@ -99,4 +104,16 @@ describe('ContributionLinkForm', () => {
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
describe('send createContributionLink with error', () => {
|
||||
beforeEach(() => {
|
||||
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
|
||||
wrapper = Wrapper()
|
||||
wrapper.vm.onSubmit()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('contributionLink.noStartDate')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -73,6 +73,7 @@
|
||||
"hide_details": "Details verbergen",
|
||||
"lastname": "Nachname",
|
||||
"math": {
|
||||
"colon": ":",
|
||||
"exclaim": "!",
|
||||
"pipe": "|",
|
||||
"plus": "+"
|
||||
@ -105,6 +106,16 @@
|
||||
"removeNotSelf": "Als Admin/Moderator kannst du dich nicht selber löschen.",
|
||||
"remove_all": "alle Nutzer entfernen",
|
||||
"save": "Speichern",
|
||||
"statistic": {
|
||||
"activeUsers": "Aktive Mitglieder",
|
||||
"deletedUsers": "Gelöschte Mitglieder",
|
||||
"name": "Statistik",
|
||||
"totalGradidoAvailable": "GDD insgesamt im Umlauf",
|
||||
"totalGradidoCreated": "GDD insgesamt geschöpft",
|
||||
"totalGradidoDecayed": "GDD insgesamt verfallen",
|
||||
"totalGradidoUnbookedDecayed": "Ungebuchter GDD Verfall",
|
||||
"totalUsers": "Mitglieder"
|
||||
},
|
||||
"status": "Status",
|
||||
"success": "Erfolg",
|
||||
"text": "Text",
|
||||
|
||||
@ -73,6 +73,7 @@
|
||||
"hide_details": "Hide details",
|
||||
"lastname": "Lastname",
|
||||
"math": {
|
||||
"colon": ":",
|
||||
"exclaim": "!",
|
||||
"pipe": "|",
|
||||
"plus": "+"
|
||||
@ -105,6 +106,16 @@
|
||||
"removeNotSelf": "As an admin/moderator, you cannot delete yourself.",
|
||||
"remove_all": "Remove all users",
|
||||
"save": "Speichern",
|
||||
"statistic": {
|
||||
"activeUsers": "Active members",
|
||||
"deletedUsers": "Deleted members",
|
||||
"name": "Statistic",
|
||||
"totalGradidoAvailable": "Total GDD in circulation",
|
||||
"totalGradidoCreated": "Total created GDD",
|
||||
"totalGradidoDecayed": "Total GDD decay",
|
||||
"totalGradidoUnbookedDecayed": "Unbooked GDD decay",
|
||||
"totalUsers": "Members"
|
||||
},
|
||||
"status": "Status",
|
||||
"success": "Success",
|
||||
"text": "Text",
|
||||
|
||||
@ -1,28 +1,83 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Overview from './Overview.vue'
|
||||
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
|
||||
import { communityStatistics } from '@/graphql/communityStatistics.js'
|
||||
import { listUnconfirmedContributions } from '@/graphql/listUnconfirmedContributions.js'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
listUnconfirmedContributions: [
|
||||
{
|
||||
pending: true,
|
||||
const apolloQueryMock = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
listUnconfirmedContributions: [
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
communityStatistics: {
|
||||
totalUsers: 3113,
|
||||
activeUsers: 1057,
|
||||
deletedUsers: 35,
|
||||
totalGradidoCreated: '4083774.05000000000000000000',
|
||||
totalGradidoDecayed: '-1062639.13634129622923372197',
|
||||
totalGradidoAvailable: '2513565.869444365732411569',
|
||||
totalGradidoUnbookedDecayed: '-500474.6738366222166261272',
|
||||
},
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
listContributionLinks: {
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Meditation',
|
||||
memo: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l',
|
||||
amount: '200',
|
||||
validFrom: '2022-04-01',
|
||||
validTo: '2022-08-01',
|
||||
cycle: 'täglich',
|
||||
maxPerCycle: '3',
|
||||
maxAmountPerMonth: 0,
|
||||
link: 'https://localhost/redeem/CL-1a2345678',
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
},
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
listUnconfirmedContributions: [
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
{
|
||||
pending: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$n: jest.fn((n) => n),
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
},
|
||||
@ -47,10 +102,30 @@ describe('Overview', () => {
|
||||
})
|
||||
|
||||
it('calls listUnconfirmedContributions', () => {
|
||||
expect(apolloQueryMock).toBeCalled()
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
query: listUnconfirmedContributions,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('commts three pending creations to store', () => {
|
||||
it('calls communityStatistics', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
query: communityStatistics,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('calls listContributionLinks', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
query: listContributionLinks,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('commits three pending creations to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 3)
|
||||
})
|
||||
|
||||
|
||||
@ -29,26 +29,39 @@
|
||||
</b-card-text>
|
||||
</b-card>
|
||||
<contribution-link :items="items" :count="count" />
|
||||
<community-statistic class="mt-5" v-model="statistics" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
|
||||
import { communityStatistics } from '@/graphql/communityStatistics.js'
|
||||
import ContributionLink from '../components/ContributionLink.vue'
|
||||
import { listUnconfirmedContributions } from '../graphql/listUnconfirmedContributions'
|
||||
import CommunityStatistic from '../components/CommunityStatistic.vue'
|
||||
import { listUnconfirmedContributions } from '@/graphql/listUnconfirmedContributions.js'
|
||||
|
||||
export default {
|
||||
name: 'overview',
|
||||
components: {
|
||||
ContributionLink,
|
||||
CommunityStatistic,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
count: 0,
|
||||
statistics: {
|
||||
totalUsers: null,
|
||||
activeUsers: null,
|
||||
deletedUsers: null,
|
||||
totalGradidoCreated: null,
|
||||
totalGradidoDecayed: null,
|
||||
totalGradidoAvailable: null,
|
||||
totalGradidoUnbookedDecayed: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getPendingCreations() {
|
||||
getPendingCreations() {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: listUnconfirmedContributions,
|
||||
@ -58,7 +71,7 @@ export default {
|
||||
this.$store.commit('setOpenCreations', result.data.listUnconfirmedContributions.length)
|
||||
})
|
||||
},
|
||||
async getContributionLinks() {
|
||||
getContributionLinks() {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: listContributionLinks,
|
||||
@ -72,9 +85,31 @@ export default {
|
||||
this.toastError('listContributionLinks has no result, use default data')
|
||||
})
|
||||
},
|
||||
getCommunityStatistics() {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: communityStatistics,
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
.then((result) => {
|
||||
this.statistics.totalUsers = result.data.communityStatistics.totalUsers
|
||||
this.statistics.activeUsers = result.data.communityStatistics.activeUsers
|
||||
this.statistics.deletedUsers = result.data.communityStatistics.deletedUsers
|
||||
this.statistics.totalGradidoCreated = result.data.communityStatistics.totalGradidoCreated
|
||||
this.statistics.totalGradidoDecayed = result.data.communityStatistics.totalGradidoDecayed
|
||||
this.statistics.totalGradidoAvailable =
|
||||
result.data.communityStatistics.totalGradidoAvailable
|
||||
this.statistics.totalGradidoUnbookedDecayed =
|
||||
result.data.communityStatistics.totalGradidoUnbookedDecayed
|
||||
})
|
||||
.catch(() => {
|
||||
this.toastError('communityStatistics has no result, use default data')
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getPendingCreations()
|
||||
this.getCommunityStatistics()
|
||||
this.getContributionLinks()
|
||||
},
|
||||
}
|
||||
|
||||
@ -1282,6 +1282,17 @@
|
||||
jest-message-util "^24.9.0"
|
||||
jest-mock "^24.9.0"
|
||||
|
||||
"@jest/fake-timers@^25.1.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185"
|
||||
integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
jest-message-util "^25.5.0"
|
||||
jest-mock "^25.5.0"
|
||||
jest-util "^25.5.0"
|
||||
lolex "^5.0.0"
|
||||
|
||||
"@jest/fake-timers@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad"
|
||||
@ -1493,6 +1504,16 @@
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^13.0.0"
|
||||
|
||||
"@jest/types@^25.5.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
|
||||
integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^3.0.0"
|
||||
|
||||
"@jest/types@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
|
||||
@ -4109,6 +4130,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
@ -7983,6 +8012,16 @@ jest-environment-jsdom-fifteen@^1.0.2:
|
||||
jest-util "^24.0.0"
|
||||
jsdom "^15.2.1"
|
||||
|
||||
jest-environment-jsdom-sixteen@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-2.0.0.tgz#0f8c12663ccd9836d248574decffc575bfb091e1"
|
||||
integrity sha512-BF+8P67aEJcd78TQzwSb9P4a73cArOWb5KgqI8eU6cHRWDIJdDRE8XTeZAmOuDSDhKpuEXjKkXwWB3GOJvqHJQ==
|
||||
dependencies:
|
||||
"@jest/fake-timers" "^25.1.0"
|
||||
jest-mock "^25.1.0"
|
||||
jest-util "^25.1.0"
|
||||
jsdom "^16.2.1"
|
||||
|
||||
jest-environment-jsdom@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b"
|
||||
@ -8197,6 +8236,20 @@ jest-message-util@^24.9.0:
|
||||
slash "^2.0.0"
|
||||
stack-utils "^1.0.1"
|
||||
|
||||
jest-message-util@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea"
|
||||
integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@jest/types" "^25.5.0"
|
||||
"@types/stack-utils" "^1.0.1"
|
||||
chalk "^3.0.0"
|
||||
graceful-fs "^4.2.4"
|
||||
micromatch "^4.0.2"
|
||||
slash "^3.0.0"
|
||||
stack-utils "^1.0.1"
|
||||
|
||||
jest-message-util@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
|
||||
@ -8219,6 +8272,13 @@ jest-mock@^24.0.0, jest-mock@^24.9.0:
|
||||
dependencies:
|
||||
"@jest/types" "^24.9.0"
|
||||
|
||||
jest-mock@^25.1.0, jest-mock@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a"
|
||||
integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
|
||||
jest-mock@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302"
|
||||
@ -8495,6 +8555,17 @@ jest-util@^24.0.0, jest-util@^24.9.0:
|
||||
slash "^2.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
jest-util@^25.1.0, jest-util@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0"
|
||||
integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
chalk "^3.0.0"
|
||||
graceful-fs "^4.2.4"
|
||||
is-ci "^2.0.0"
|
||||
make-dir "^3.0.0"
|
||||
|
||||
jest-util@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1"
|
||||
@ -8741,7 +8812,7 @@ jsdom@^15.2.1:
|
||||
ws "^7.0.0"
|
||||
xml-name-validator "^3.0.0"
|
||||
|
||||
jsdom@^16.4.0:
|
||||
jsdom@^16.2.1, jsdom@^16.4.0:
|
||||
version "16.7.0"
|
||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
|
||||
integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==
|
||||
@ -9096,6 +9167,13 @@ loglevel@^1.6.8:
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
|
||||
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
|
||||
|
||||
lolex@^5.0.0:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
|
||||
integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
loose-envify@^1.0.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
|
||||
@ -10,7 +10,7 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0046-adapt_users_table_for_gradidoid',
|
||||
DB_VERSION: '0047-messages_tables',
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
|
||||
11
backend/src/graphql/enum/MessageType.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum ContributionMessageType {
|
||||
HISTORY = 'HISTORY',
|
||||
DIALOG = 'DIALOG',
|
||||
}
|
||||
|
||||
registerEnumType(ContributionMessageType, {
|
||||
name: 'ContributionMessageType',
|
||||
description: 'Name of the Type of the ContributionMessage',
|
||||
})
|
||||
@ -207,7 +207,7 @@ describe('UserResolver', () => {
|
||||
it('sets "de" as default language', async () => {
|
||||
await mutate({
|
||||
mutation: createUser,
|
||||
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'fr' },
|
||||
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'it' },
|
||||
})
|
||||
await expect(User.find()).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
|
||||
@ -49,7 +49,7 @@ const isPassword = (password: string): boolean => {
|
||||
return !!password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9 \\t\\n\\r]).{8,}$/)
|
||||
}
|
||||
|
||||
const LANGUAGES = ['de', 'en', 'es']
|
||||
const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl']
|
||||
const DEFAULT_LANGUAGE = 'de'
|
||||
const isLanguage = (language: string): boolean => {
|
||||
return LANGUAGES.includes(language)
|
||||
|
||||
89
database/entity/0047-messages_tables/Contribution.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import Decimal from 'decimal.js-light'
|
||||
import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
DeleteDateColumn,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
} from 'typeorm'
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { User } from '../User'
|
||||
import { ContributionMessage } from '../ContributionMessage'
|
||||
|
||||
@Entity('contributions')
|
||||
export class Contribution extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ unsigned: true, nullable: false, name: 'user_id' })
|
||||
userId: number
|
||||
|
||||
@ManyToOne(() => User, (user) => user.contributions)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user: User
|
||||
|
||||
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', name: 'created_at' })
|
||||
createdAt: Date
|
||||
|
||||
@Column({ type: 'datetime', nullable: false, name: 'contribution_date' })
|
||||
contributionDate: Date
|
||||
|
||||
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||
memo: string
|
||||
|
||||
@Column({
|
||||
type: 'decimal',
|
||||
precision: 40,
|
||||
scale: 20,
|
||||
nullable: false,
|
||||
transformer: DecimalTransformer,
|
||||
})
|
||||
amount: Decimal
|
||||
|
||||
@Column({ unsigned: true, nullable: true, name: 'moderator_id' })
|
||||
moderatorId: number
|
||||
|
||||
@Column({ unsigned: true, nullable: true, name: 'contribution_link_id' })
|
||||
contributionLinkId: number
|
||||
|
||||
@Column({ unsigned: true, nullable: true, name: 'confirmed_by' })
|
||||
confirmedBy: number
|
||||
|
||||
@Column({ nullable: true, name: 'confirmed_at' })
|
||||
confirmedAt: Date
|
||||
|
||||
@Column({ unsigned: true, nullable: true, name: 'denied_by' })
|
||||
deniedBy: number
|
||||
|
||||
@Column({ nullable: true, name: 'denied_at' })
|
||||
deniedAt: Date
|
||||
|
||||
@Column({
|
||||
name: 'contribution_type',
|
||||
length: 12,
|
||||
nullable: false,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
contributionType: string
|
||||
|
||||
@Column({
|
||||
name: 'contribution_status',
|
||||
length: 12,
|
||||
nullable: false,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
contributionStatus: string
|
||||
|
||||
@Column({ unsigned: true, nullable: true, name: 'transaction_id' })
|
||||
transactionId: number
|
||||
|
||||
@DeleteDateColumn({ name: 'deleted_at' })
|
||||
deletedAt: Date | null
|
||||
|
||||
@OneToMany(() => ContributionMessage, (message) => message.contribution)
|
||||
@JoinColumn({ name: 'contribution_id' })
|
||||
messages?: ContributionMessage[]
|
||||
}
|
||||
46
database/entity/0047-messages_tables/ContributionMessage.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm'
|
||||
import { Contribution } from '../Contribution'
|
||||
|
||||
@Entity('contribution_messages', {
|
||||
engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
|
||||
})
|
||||
export class ContributionMessage extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'contribution_id', unsigned: true, nullable: false })
|
||||
contributionId: number
|
||||
|
||||
@ManyToOne(() => Contribution, (contribution) => contribution.messages)
|
||||
@JoinColumn({ name: 'contribution_id' })
|
||||
contribution: Contribution
|
||||
|
||||
@Column({ name: 'user_id', unsigned: true, nullable: false })
|
||||
userId: number
|
||||
|
||||
@Column({ length: 2000, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||
message: string
|
||||
|
||||
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', name: 'created_at' })
|
||||
createdAt: Date
|
||||
|
||||
@Column({ type: 'datetime', default: null, nullable: true, name: 'updated_at' })
|
||||
updatedAt: Date
|
||||
|
||||
@DeleteDateColumn({ name: 'deleted_at' })
|
||||
deletedAt: Date | null
|
||||
|
||||
@Column({ name: 'deleted_by', default: null, unsigned: true, nullable: true })
|
||||
deletedBy: number
|
||||
|
||||
@Column({ length: 12, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||
type: string
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export { Contribution } from './0045-add_denied_type_and_status_to_contributions/Contribution'
|
||||
export { Contribution } from './0047-messages_tables/Contribution'
|
||||
|
||||
1
database/entity/ContributionMessage.ts
Normal file
@ -0,0 +1 @@
|
||||
export { ContributionMessage } from './0047-messages_tables/ContributionMessage'
|
||||
@ -8,6 +8,7 @@ import { User } from './User'
|
||||
import { UserContact } from './UserContact'
|
||||
import { Contribution } from './Contribution'
|
||||
import { EventProtocol } from './EventProtocol'
|
||||
import { ContributionMessage } from './ContributionMessage'
|
||||
|
||||
export const entities = [
|
||||
Contribution,
|
||||
@ -19,5 +20,6 @@ export const entities = [
|
||||
TransactionLink,
|
||||
User,
|
||||
EventProtocol,
|
||||
ContributionMessage,
|
||||
UserContact,
|
||||
]
|
||||
|
||||
30
database/migrations/0047-messages_tables.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* MIGRATION TO CREATE THE MESSAGES TABLES
|
||||
*
|
||||
* This migration creates the `messages` tables in the `community_server` database (`gradido_community`).
|
||||
* This is done to keep all data in the same place and is to be understood in conjunction with the next migration
|
||||
* `0046-messages_tables` which will fill the tables with the existing data
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
CREATE TABLE IF NOT EXISTS \`contribution_messages\` (
|
||||
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
\`contribution_id\` int(10) unsigned NOT NULL,
|
||||
\`user_id\` int(10) unsigned NOT NULL,
|
||||
\`message\` varchar(2000) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
\`created_at\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
\`updated_at\` datetime DEFAULT NULL,
|
||||
\`deleted_at\` datetime DEFAULT NULL,
|
||||
\`deleted_by\` int(10) unsigned DEFAULT NULL,
|
||||
\`type\` varchar(12) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT "DIALOG",
|
||||
PRIMARY KEY (\`id\`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`DROP TABLE IF EXISTS \`contribution_messages\`;`)
|
||||
}
|
||||
219
docu/Concepts/BusinessRequirements/UC_Contribution_Messaging.md
Normal file
@ -0,0 +1,219 @@
|
||||
# Contribution Messaging
|
||||
|
||||
Die Idee des *Contribution Messagings* besteht darin, dass ein User an eine existierende Contribution eine von ihm erfasste Nachricht anhängen kann. Als Ursprungsmotivation dieses *Contribution Messagings* ist eigentlich die Kommunikation zwischen dem Support-Mitarbeiter und dem Ersteller der Contribution gedacht. Doch es sind auch Nachrichten anderer User in dieser Kommunikationkette denkbar, um beispielsweise Bemerkungen, Kritik und Anregungen zu erstellten Contributions zu hinterlassen. Dadurch soll das Miteinander innerhalb einer Community für die geleisteten Gemeinwohl-Aktivitäten in den Vordergrund gerückt werden.
|
||||
|
||||
## Hinweis
|
||||
|
||||
In diesem Dokument werden alle zu dem Thema besprochenen und angedachten Anforderungen, unabhängig in welcher Ausbaustufe diese umgesetzt werden, beschrieben. Für die weitere Entwicklung und zur Erstellung der Technischen Tickets werden die einzelnen Anforderungen mit einem Hinweis - Beispiel [AS-1] für Ausbaustufe 1- zur geplanten Ausbaustufe direkt angeben. Die Markierung [AS-x] deutet auf eine Anforderung in einer nächsten Stufe hin. Wobei die Priorisierung und damit noch nicht klar ist, in welcher Ausbaustufe diese Anforderung letztendlich wirklich umgesetzt wird.
|
||||
|
||||
## Allgemeine Anforderungen
|
||||
|
||||
### Contribution
|
||||
|
||||
* **[AS-1]** Eine Contribution bekommt zu den aktuell implementierten drei Status-Werten (eingereicht, bestätigt und abgelehnt) noch einen vierten Status (in Arbeit) hinzu
|
||||
|
||||
* eingereicht (pending): Die Contribution wurde vom User bzw. vom Moderator neu erfasst und wartet auf Bearbeitung der Moderatoren
|
||||
* in Arbeit (inprogress): Die Contribution wurde von einem Moderator in Bearbeitung genommen, in dem er eine Rückfrage als Nachricht an den User erfasst hat und wartet auf Beantwortung vom User, dem die Contribution zugeordnet ist
|
||||
* bestätigt (confirmed): Die Contribution wurde von einem Moderator genehmigt und der Betrag ist dem User der Contribution schon gutgeschrieben. Dies ist eine Ende-Zustand auf den keine weitere Bearbeitung mehr folgt. **[AS-x]** Es kann selbst nach einer Bestätigung der Contribution noch eine neue Nachricht dazu erfasst werden.
|
||||
* abgelehnt (denied): die Contribution wurde vom Moderator abgelehnt und es hat keine Gutschrift des Betrages auf dem Konto des Users stattgefunden. Dies ist ein Ende-Zustand auf den keine weitere Bearbeitung mehr folgt. **[AS-x]** Es kann selbst nach einer Ablehnung der Contribution noch eine neue Nachricht dazu erfasst werden.
|
||||
* gelöscht (deleted): die Contribution wurde von einem Moderator oder dem User gelöscht. Dies kann zu jeder Zeit aus dem Status pending oder inprogress nicht aber aus dem Status confirmed oder denied heraus initiiert werden und ist unabhängig, ob an der Contribution schon Messages anhängig sind. Das Löschen wird als Soft-Delete implementiert in dem die Attribute *deletedAt* und *deletedBy* gesetzt werden. Im Status deleted kommt die Contribution nicht mehr zur Anzeige, bleibt aber in der Datanbank erhalten.
|
||||
* **[AS-1]** Sobald ein Moderator eine Contribution zur Bearbeitung anzeigt, wird sofort die ModeratorId und/oder der neue Status in die Contribution gespeichert, um diese für andere Moderatoren zu sperren. Dies ist notwendig, um fälschlicherweise ein paralleles Bearbeiten einer Contribution durch mehrere Moderatoren zu verhindern.
|
||||
* **[AS-1]** Bei der Bestätigung einer Contribution wird neben der Status-Änderung auf *confirmed* eine neue Nachricht erzeugt. Diese Nachricht enthält einen Bestätigungstext, der im Frontend als Standard-Bestätigungstext vorbelegt ist und vom Moderator für eine individuelle Bestätigung überschrieben werden kann. Das Speichern der Contribution-Bestätigung erfolgt nach dem die zugehörige Schöpfung als Transaktion erfolgreich gespeichert ist. Falls es beim Speichern der Gutschrift und/oder der Contribution inkl. Message zu einem Fehler kommt, darf weder die Contribution noch die Transaktion gespeichert werden, sondern es muss eine Fehlermeldung ohne Änderungen der Datanbankdaten erfolgen.
|
||||
* **[AS-1]** Bei der Ablehnung einer Contribution wird neben der Status-Änderung auf *denied* eine neue Nachricht erzeugt. Diese Nachricht enthält einen Ablehnungtext, der im Frontend als Standard-Ablehnungstext vorbelegt ist und vom Moderator für eine individuelle Begründung überschrieben werden kann.
|
||||
* **[AS-1]** Zu einer Contribution können nur der besitzende User und die Moderatoren eine Nachricht erfassen. **[AS-x]** Für evtl. Bemerkungen, Kritik und Anregungen kann ein beliebiger User über die *Gemeinschaft*-Anzeige auch Nachrichten zu Contributions anderer User erfassen.
|
||||
* **[AS-1]** Eine Contribution kann vom User der Contribution oder einem Moderator gelöscht werden, egal ob an der Contribution schon Nachrichten zugeordnet sind oder nicht. Beim Löschen muss der User bzw. Moderator eine Begründung formulieren, die beim Löschen dann als Nachricht an die Contribution noch angehängt wird. Dies dient der besseren Nachvollziehbarkeit im Falle von nachträglichen Rückfragen bzw. Analysen.
|
||||
* **[AS-1]** Eine Contribution zeigt in der Normal-Ansicht per farblichem Icon bzw. textuell, ob
|
||||
|
||||
* noch keine Nachrichten existieren (graue Sprechblase)
|
||||
* mindestens eine Nachricht existiert (blaue Sprechblase)
|
||||
* **[AS-x]** Gesamtsanzahl der existierenden Nachrichten (z.B. Zahl im bzw. neben Sprechbalsen-Icon)
|
||||
* **[AS-x]** Anzahl der ungelesenen Nachrichten (z.B. Zahl/Zahl im bzw. neben Sprechblasen-Icon)
|
||||
* **[AS-1]** Das Bearbeiten einer Contribution ist im Status *eingereicht* möglich. Solange noch keine Nachrichten anhängig sind, wird nach dem Bearbeiten keine weitere Aktion notwendig, es erfolgt noch keine Historisierung. Sobald aber schon mindestens eine Nachricht anhängig ist, muss eine Historisierung der Contribution-Bearbeitung erfolgen. Das Ziel der Historisierung ist die jeweilige Version einer Contribution vor der Bearbeitung als Message in die Nachrichtenliste einzutragen und dabei diese HISTORY-Message so zeitlich einzusortieren, dass diese mit den DIALOG-Messages inhaltlich korrespondiert. Das bedeutet, dass beim Starten der Bearbeitung der original Contributiontext und -Betrag als Nachricht mit besonderer Kennzeichnung (*type* = HISTORY) und mit Zeitstempel für Contribution-Version 1: *Message.createdAt* = Contribution.createdAt bzw. für Contribution-Version >1: *Message.createdAt* = Contribution.updatedAt erzeugt und angehängt wird. Der geänderte bzw. neue Inhalt der Contribution wird in der Contribution selbst gespeichert und das *updatedAt* und *updatedBy* der Contribution wird aktualisiert.
|
||||
* **[AS-1]** Folgende Status-Übergänge sind für eine Contribution möglich:
|
||||
|
||||

|
||||
* **[AS-x]** Für das Bearbeiten der Contributions im AdminInterface bedarf es einer Möglichkeit der Synchronisierung zwischen den Moderatoren. Dazu kann in die Contribution eine *workerId* eingetragen werden, die jedem Moderator zeigt, wer aktuell diese Contribution in Bearbeitung hat. Diese Information ist lediglich informativ, denn jeder Moderator kann sich selbst als workerId eintragen, um beispielsweise die Bearbeitung, bei längerer Abwesenheit des aktuell bearbeitenden Moderators, selbst zu übernehmen. Das AdminInterface bietet dazu für jede Contribution neben der Anzeige des aktuell bearbeitenden Moderators (Name aus User-Daten über *workerId* ermittelt) eine Möglichkeit die eigene ModeratorId als *workerId* in die Contribution einzutragen. Ein Wechsel der *workerId* hat keine Auswirkungen auf den aktuellen Status der Contribution.
|
||||
|
||||
### Nachrichten
|
||||
|
||||
* **[AS-1]** Die Nachrichten zu einer Contribution werden als Detail-Ansicht der Contribution zeitlich absteigend - per `createdAt` - sortiert als einfache Liste angezeigt, das heißt die neueste Nachricht steht oben.
|
||||
* **[AS-1]** Nur der User, dem die Contribution zugeordnet ist und alle Moderatoren können Nachrichten zu einer Contribution verfassen und bearbeiten. Alle anderen User haben nur Leserechte auf die Contributions und Nachrichten anderer User.
|
||||
* **[AS-x]** Jeder beliebige User kann zu einer Contribution, egal in welchem Status diese sich befindet, eine Nachricht erstellen.
|
||||
* **[AS-1]** In den Anzeigen der Contribution-Listen "*Meine Beiträge*" und "*Gemeinschaft*" kann jede enthaltene Contribution analog der Detail-Ansicht einer Transaktion aufgeklappt werden, um die schon angehängten Nachrichten anzuzeigen.
|
||||
* **[AS-1]** Mit der Detail-Ansicht einer Contribution wird auch ein Button eingeblendet, über den die Erfassung einer neuen Nachricht gestartet wird.
|
||||
* **[AS-1]** Mit der Anzeige einer Nachricht wird auch der Ersteller und der Erstellungszeitpunkt dieser Nachricht angezeigt.
|
||||
* **[AS-1]** Analog zur Detailansicht einer Contribution in der Wallet wird auch eine Detailansicht der Contributions im Admin-Bereich eingebaut.
|
||||
* **[AS-1]** Die Länge einer Nachricht wird auf maximal 2000 Zeichen begrenzt
|
||||
* **[AS-1]** Die Nachrichten werden als einfach verkettete Liste an die Contribution angehängt. Es wird keine Untertützung von Nachrichten an Nachrichten geben
|
||||
* **[AS-1]** Eine Nachricht unterscheidet sich im Typ, ob es eine vom User/Moderator erstellte DIALOG-Message oder ob es eine vom System automatisierte HISTORY-Message ist. Nachrichten, die während einer Bestätigung bzw. Ablehnung einer Contribution mit vordefinierten Texten vom System erstellt werden sind dennoch vom Typ DIALOG, denn der vordefinierte Text kann vom Moderator auch manuell überschrieben bzw. angepasst werden.
|
||||
* **[AS-1]** Ist die letzte, sprich jüngste Nachricht eine manuell erstellte Nachricht, kann diese vom Ersteller der Nachricht - `Messages.userId` - nachträglich bearbeitet werden. Neben dem geänderten Nachrichtentext wird der Zeitpunkt der Änderung im Feld `updatedAt `erfasst. Die Einsortierung in der Nachrichtenliste bleibt auch bei einer nachträglichen Änderung auf dem Feld `createdAt`.
|
||||
* **[AS-x]** Eine existente Nachricht kann nur von einem Moderator gelöscht werden - Unterbindung von unliebsamen Troll-Inhalten. Dabei wird ein SoftDelete ausgeführt und das Attribut *deleted_at* und *deleted_by* mit dem Zeitpunkt des Löschens und mit der UserId des Moderators gesetzt.
|
||||
* **[AS-x]** Ist die jüngste Nachricht der Liste eine DIALOG-Nachricht, dann kann diese vom User, der sie erstellt hat, nachträglich bearbeiten werden. In der Nachrichtenanzeige wird dazu ein Stift-Icon sichtbar, das die Anzeige der Nachricht in einen Bearbeitungsmodus versetzt, der den vorhandenen Text plus einen Abbruch- und einen Speichern-Button anzeigt. Mit Beenden des Bearbeitungsmodus per Speichern-Button wird der geänderte Text und der aktuelle Zeitpunkt in das *updated_at* gespeichert.
|
||||
* **[AS-x]** Die Verwaltung ob eine Nachricht neu ist und ob diese schon vom User/Moderator angezeigt sprich gelesen wurde, muss für eine spätere Ausbaustufe noch im Detail spezifiziert werden. Die einfache Variante mit einem Attribut *presentedAt* würde nur für den User funktionieren, nicht aber für zusätzlich mehrere Moderatoren und ggf. später für andere User.
|
||||
|
||||
## Contribution Ansichten
|
||||
|
||||
### Contribution-Liste "MeineBeiträge" Ansicht
|
||||
|
||||
Die Contributions werden in den User-Ansichten "Meine Beiträge" und "Gemeinschaft", sowie im Admin-Interface als Listen angezeigt. Das nachfolgende Bild zeigt beispielhaft eine Contribution-List in der "Meine Beiträge"-Ansicht.
|
||||
|
||||

|
||||
|
||||
Die Liste der Contributions enthält vier Contributions, je eine in den vier verschiedenen Darstellungsarten "eingereicht", "in Bearbeitung", "bestätigt" und "abgelehnt". Die ersten beiden Contributions im Status "eingereicht" und "in Bearbeitung" können nachträglich noch bearbeitet oder gar gelöscht werden - zu erkennen an den Icons "Stift" und "Mülleimer". Diese Möglichkeit besteht bei den beiden anderen Contributions nicht mehr, da diese schon vom Support entsprechend bestätigt oder gar abgelehnt wurden.
|
||||
|
||||
Das Icon Sprechblase in der Farbe grau deutet darauf hin, dass es zu dieser Contribution noch keine gespeicherten Nachrichten gibt. Ist das Sprechblasen-Icon blau, dann existieren zu der Contribution schon gespeicherte Nachrichten. Ein Klick auf das Sprechblasen-Icon öffnet die Nachrichten-Ansicht der entsprechenden Contribution
|
||||
|
||||
**[AS-x]** Ist zu der Farbe blau auch noch eine Zahl, wie bei der zweiten Contribution sichtbar, dann ist in der Nachrichtenliste dieser Contribution eine neue Nachricht enthalten, die noch nicht vom User zur Anzeige gebracht wurde. Ein Klick auf das Sprechblasen-Icon öffnet die Nachrichten-Ansicht der entsprechenden Contribution, wodurch ein Update aller neuen noch ungelesenen Nachrichten erfolgt, in dem der Zeitpunkt der Anzeige in das Attribut *presented_at* eingetragen wird. Gleichzeit wird auch dadurch die Zahl unterhalb des blauen Sprechblasen-Icons gelöscht analog der dritten und vierten Contribution.
|
||||
|
||||
## Contribution-Liste "Gemeinschaft" Ansicht
|
||||
|
||||
Im Unterschied zur Contributions-Listen Ansicht "Meine Beiträge" wird in der "Gemeinschaft"-Ansicht jeder Contribution zusätzlich der User, dem die Contribution zugeordnet ist, angezeigt.
|
||||
|
||||

|
||||
|
||||
Als User-Information kann der Vorname und Nachname oder sofern vorhanden auch der Alias angezeigt werden.
|
||||
|
||||
### Contribution-LifeCycle
|
||||
|
||||
#### Erfassung
|
||||
|
||||
Sobald auf der Seite "Schreiben" eine neue Contribution erfasst wurde, wird diese auf der Seite "Meine Beiträge" im Status "eingereicht" angzeigt, wie im nachfolgenden Bild mit den ersten beiden Contributions dargestellt. Bei der Erfassung der Contribution wird neben den fachlichen Attributen wie Memo und Betrag gleichzeitig das Feld `created_at` mit dem aktuellen Zeitpunkt und das Feld `user_id` bzw `moderator_id` mit der UserId des aktuell angemeldeten Users bzw des Support-Moderators eingetragen.
|
||||
|
||||

|
||||
|
||||
#### In Bearbeitung
|
||||
|
||||
Sobald ein Moderator im Admin-Interface eine Contribution im Status "*eingereicht*" zur Anzeige bringt, wird das Feld *inprogress_at* mit dem aktuellen Zeitpunkt und das Feld *inprogress_by* mit der UserId des Moderators gespeichert. Dadurch wird verhindert, dass ein paralleles Bearbeiten einer pending Contribution durch den Support stattfindet.
|
||||
|
||||
Falls der Moderator eine Rückfrage an den User richten möchte, zum Beispiel weil die Beschreibung der Aktivität nicht ganz klar ist, dann erfasst der Moderator einen Rückfragetext, der als Nachricht an die Contribution in die Tabelle `contribution_messages` geschrieben wird. Die Nachricht enthält neben dem Text im Feld `message `und dem Zeitpunkt im Feld `created_at`, die ModeratorID im Feld `user_id `und den Bezug zur Contribution im Feld `contribution_id`. Im Feld `type `wird das Enum 'DIALOG' eingetragen, um für die Anzeige in der Nachrichten-Liste das gewünschte Anzeigelayout gegenüber Nachrichten vom Typ 'HISTORY' zu verwenden.
|
||||
|
||||
Eine so erfasste Rückfrage vom Support kann der User durch das Icon "?" erkennen, auch steht unterhalb des nun blauen Sprechblasen-Icons die Anzahl der neuen ungelesenen Nachrichten. Mit Klick auf das Sprechblasen-Icon wird die Detailansicht der Contribution geöffnet und die angehängte(n) Nachricht(en) mit der Rückfrage sichtbar. Durch das Öffnen der Detailansicht und der Anzeige der Nachrichten werden in alle noch ungelesenen Nachrichten der aktuelle Zeitpunkt in das Attribut *presented_at* geschrieben und die Zahl unterhalb des Sprechblasen-Icons gelöscht.
|
||||
|
||||

|
||||
|
||||
Der User kann über das Stift-Icon die Contribution bearbeiten oder über das Mülleimer-Icon die Contribution löschen. Auch das Erfassen einer Nachricht an den Support wäre über das Sprechblasen-Icon mit den 3 Punkten denkbar. Das Besondere beim Speichern der bearbeiteten Contribution bzw. einer erfassten Nachrichten ist die implizite Umsetzung des Contribution-Status wieder zurück auf den Wert "*eingereicht*", damit der Support die Bearbeitung der Contribution wieder erkennen kann und übernimmt.
|
||||
|
||||
Nach der Bearbeitung der Contribution durch den User wird beim Speichern der ursprüngliche Inhalt der Contribution als Nachricht vom Type 'HISTORY' angehängt, der dann optisch sich auch von den anderen Dialog-Nachrichten unterscheidet - hier mit anderem Hintergrund und Schriftfarbe als eine 'DIALOG'-Nachricht. Egal von welchem Typ eine Nachricht ist - HISTORY oder DIALOG -, sie wird immer über das Attribut *created_at* einsortiert.
|
||||
|
||||

|
||||
|
||||
#### Bestätigen / Ablehnen
|
||||
|
||||
Sobald der Support wieder die Contribution im Status "*eingereicht*" zur weiteren Bearbeitung hat, kann der Moderator diese entweder "*bestätigen*" oder "*ablehnen*". Im Admin-Interface hat der Moderator dazu jeweils einen Button, über den er die weitere Verarbeitungslogik startet. Das Frontend öffnet dazu ein Dialog, in dem ein Standardtext für die Bestätigung bzw. für die Ablehnung vorbelegt ist. Dieser kann durch direktes Betätigen des "*Bestätigen*"-Button bzw. des "*Ablehnen*"-Buttons übernommen oder vor dem Betätigen der Buttons den vorgeschlagene Text durch eine individuelle Begründung überschrieben werden. Der Begründungstext wird in beiden Fällen als 'DIALOG'-Nachricht an die Contribution angehängt, wie im nächsten Bild sichtbar.
|
||||
|
||||

|
||||
|
||||
### Contribution-MessageList Ansicht und Bearbeitung
|
||||
|
||||
Mit Klicken auf das Sprechblasen-Icon kann die Nachrichten-Ansicht der Contribution geöffnet, wie im nachfolgenden Bild dargestellt und auch wieder geschlossen werden.
|
||||
|
||||

|
||||
|
||||
Bei geöffneter Nachrichten-Ansicht wird unterhalb der Contribution eine Kopfzeile "Nachrichten" und darunter die Liste der Nachrichten chronologisch absteigend nach ihrem `createdAt`-Datum sortiert angezeigt. Pro Nachricht ist der Absender, der Zeitstempel der Nachrichtenerstellung und der Nachrichtentext zu sehen.
|
||||
|
||||
Eine einmal erstellte Nachricht kann vom User selbst zwar nicht mehr gelöscht, aber sie kann als jüngste Nachricht in der Nachrichtenliste vom Ersteller noch einmal bearbeitet werden, wie an dem sichtbaren Stift-Icon in der Nachricht zu erkennen ist. Mit Klick auf das Stift-Icon wechselt die Anzeige der Nachricht in den Bearbeitungsmodus (analog dem Erfassen einer neuen Nachricht wie im nächsten Kapitel), in dem der Text nun verändert werden kann und zwei angezeigte Buttons zum Speichern bzw zum Verwerfen der Änderungen. Sobald eine Nachricht nachträglich bearbeitet wurde, wird der Zeitpunkt der Bearbeitung zusätzlich mit dem Label "bearbeitet am: < Zeitpunkt >" hinter dem Creation-Zeitpunkt angezeigt.
|
||||
|
||||
Um der Gefahr von unliebsamen Inhalten z.B. durch Trolle zu begegenen, kann ein Moderator eine Nachricht löschen. Es wird dabei ein SoftDelete ausgeführt, wodurch in die Nachricht das Attribut *deleted_at* und *deleted_by* mit dem Zeitpunkt des Löschens und mit der UserId des Moderators gesetzt wird.
|
||||
|
||||
### Contribution-CreateMessage Ansicht
|
||||
|
||||
Man kann aber mit Klicken auf den Button rechts - Sprechblasen-Icon mit den Punkten - in der Nachrichten-Kopfzeile eine neue Nachricht erstellen, siehe dazu nächstes Bild.
|
||||
|
||||

|
||||
|
||||
Es wird mit Klicken auf das Sprechblasen-Icon mit den drei Punkten ein neues Nachrichten-Fenster direkt unterhalb der Nachrichten-Kopfzeile eingeblendet. Das Textfeld ist leer und enthält lediglich den eingeblendeten Hinweis-Text, der mit Beginn der Texteingabe sofort verschwindet. Über die beiden Buttons rechts unten im Nachrichten-Eingabefenster kann die eingegebene Nachricht gespeichert oder verworfen werden. Sobald die neue Nachricht gespeichert wurde, erscheint diese analog den schon vorhandenen Nachrichten mit dem gleichen Erscheinungsbild und ohne Speicher- oder Verwerfen-Button. Der User kann beliebig viele Nachrichten eingeben, es gibt hierzu keine Begrenzung bzw. Validierung.
|
||||
|
||||
## Contribution-Message Services
|
||||
|
||||
### searchContributions of User
|
||||
|
||||
Mit diesem Service werden alle Contributions aber ohne Messages, die dem einen User zugeordnet sind gelesen und nach ihrem CreatedAt-Datum zeitlich absteigend sortiert. Ein mögliches Paged-Reading über eine größere Menge an Contributions muss dabei möglich sein. Es werden weitere evtl. transiente Informationen pro Contribution mit geliefert, um die entsprechenden Ausprägungen im Frontend ansteuern zu können:
|
||||
|
||||
* Status: eingereicht / in Bearbeitung / bestätigt / abgelehnt
|
||||
* Messages vorhanden: ja / nein
|
||||
* **[AS-x]** falls Messages vorhanden, wieviele davon sind ungelesen
|
||||
|
||||
### searchContributions for all
|
||||
|
||||
Mit diesem Service werden alle Contributions aber ohne Messages, aller User gelesen und nach ihrem CreatedAt-Datum zeitlich absteigend sortiert. Ein mögliches Paged-Reading über eine größere Menge an Contributions muss dabei möglich sein. Es werden weitere evtl. transiente Informationen pro Contribution mit geliefert, um die entsprechenden Ausprägungen im Frontend ansteuern zu können:
|
||||
|
||||
* Status: eingereicht / in Bearbeitung / bestätigt / abgelehnt
|
||||
* Messages vorhanden: ja / nein
|
||||
* **[AS-x]** falls Messages vorhanden, wieviele davon sind ungelesen
|
||||
* User-Info: Vorname, Nachname oder sofern vorhanden dann der Alias
|
||||
|
||||
### updateContribution
|
||||
|
||||
Über diesen Service kann eine Contribution nur verändert werden, wenn sie im Status "*eingereicht*" oder "*in Bearbeitung*" ist. Dies kann nur der Ersteller der Contribution selbst ausführen. Implizit wird dabei zur Historisierung eine 'HISTORY'-Nachricht mit dem ürsprünglichen Inhalt der Contribution erstellt. In der bearbeiteten Contribution wird neben den geänderten Attributen `Memo `und `Amount `das `created_at`-Datum auf den Zeitpunkt des Updates gesetzt.
|
||||
|
||||
### deleteContribution
|
||||
|
||||
Mit diesem Service kann eine Contribution im Status "*eingereicht*" bzw. "*in Bearbeitung*" und nur vom Ersteller selbst oder von einem Moderator gelöscht werden. Dabei wird nur ein SoftDelete durchgeführt, indem das `deleted_at`-Datum auf den aktuellen Zeitpunkt und das `deleted_by`-Attribut mit der UserId des Users/Moderators gesetzt wird.
|
||||
|
||||
### confirmContribution
|
||||
|
||||
Über diesen Service kann der Moderator im AdminInterface eine "*eingereichte*" bzw. eine "*in Bearbeitung*" Contribution bestätigen. Dabei wird implizit das Attribut "`confirmed_by`" auf die UserId des Moderators gesetzt und das Attribut "`confirmed_at`" auf den aktuellen Zeitpunkt. Zusätzlich wird eine Nachricht mit dem übergebenen Begründungstext der Confirmation an die Contribution angehängt.
|
||||
|
||||
### denyContribution
|
||||
|
||||
Über diesen Service kann der Moderator im AdminInterface eine "*eingereichte*" bzw. ein "*in Bearbeitung*" Contribution ablehnen. Dabei wird implizit das Attribut "`denied_by`" auf die UserId des Moderators, das Attribut "`denied_at`" auf den aktuellen Zeitpunkt gesetzt. Zusätzlich wird eine Nachricht mit dem übergebenen Begründungstext der Ablehnung an die Contribution angehängt.
|
||||
|
||||
### searchContributionMessages
|
||||
|
||||
Dieser Service liefert zu einer bestimmten Contribution alle gespeicherten Nachrichten chronologisch nach dem `CreatedAt`-Datum absteigend sortiert. Neben dem Nachrichtentext und dem CreatedAt-Datum wird auch der User, der die Nachricht erstellt hat, geliefert. Als User-Daten wird entweder der Vorname und Nachname oder falls vorhanden der Alias geliefert. Ein mögliches Paged-Reading über eine größere Menge an Messages muss dabei möglich sein.
|
||||
|
||||
### createMessageForContribution
|
||||
|
||||
Über diesen Service kann zu einer bestimmten Contribution eine neue Nachricht gespeichert werden. Neben dem Nachrichten-Text wird die `contribution_id`, die `user_id `des Nachrichten Erstellers, der `type `der Nachricht und das `created_at`-Datum gespeichert.
|
||||
|
||||
### UpdatePresentedMessagesOfContribution
|
||||
|
||||
**[AS-x]** Die Verwaltung wer hat wann welche Nachricht angezeigt bzw. welche Nachricht ist für ein User bzw ein Moderator noch neu und ungelesen bedarf einer komplexeren Verwaltung, die auf eine zukünftige Ausbaustufe verschoben wird. Es wird dabei eine Many-To-Many Beziehung zwischen einer Message und einem User benötigt.
|
||||
|
||||
Ein einfacher Ansatz, der aber nur für den User funktionieren könnte, wäre mit dem Attribut *presented_at* in der Message. Mit diesem Service werden alle Nachrichten einer Contribution mit *presented_at* = null aktualisiert, in dem der aktuelle Zeitpunkt in das Attribut *presented_at* eingetragen wird.
|
||||
|
||||
## Datenbank Anpassungen
|
||||
|
||||
Das Class-Diagramm der beteiligten Tabellen gibt einen ersten Eindruck:
|
||||
|
||||

|
||||
|
||||
### Contributions Tabelle
|
||||
|
||||
Die Contribution-Tabelle benötigt für die Speicherung der verschiedenen Status-Constellationen folgende zusätzliche bzw. vorhandene Attribute:
|
||||
|
||||
inprogress_at: **[neues Attribut]** speichert den Zeitpunkt, wann die Contribution durch den Support-Mitarbeiter *in Arbeit genommen* wurde
|
||||
|
||||
inprogress_by: **[neues Attribut]** speichert die UserId des Moderators, der die Contribution *in Arbeit genommen* hat
|
||||
|
||||
confirmed_at: [vorhandenes Attribut] speichert den Zeitpunkt, wann die Contribution durch den Support-Mitarbeiter *bestätigt* wurde
|
||||
|
||||
confirmed_by: [vorhandenes Attribut] speichert die UserId des Moderators, der die Contribution *bestätigt* hat
|
||||
|
||||
denied_at: [vorhandenes Attribut] speichert den Zeitpunkt, wann die Contribution *abgelehnt* wurde
|
||||
|
||||
denied_by: [vorhandenes Attribut] speichert die UserId des Moderators, der die Contribution *abgelehnt* hat
|
||||
|
||||
deleted_at: **[neues Attribut]** speichert den Zeitpunkt, wann die Contribution *gelöscht* wurde
|
||||
|
||||
deleted_by: **[neues Attribut]** speichert die UserId des Moderators, der die Contribution *gelöscht* hat
|
||||
|
||||
### ContributionMessages Tabelle
|
||||
|
||||
Die ContributionMessages Tabelle ist gänzlich neu mit allen Attributen:
|
||||
|
||||
id: technical primary key
|
||||
|
||||
contribution_id: foreign key to contributions table the message belong to
|
||||
|
||||
user_id: foreign key to user table the message was created by
|
||||
|
||||
message: the message-text
|
||||
|
||||
created_at: the point of the time the message entry was created
|
||||
|
||||
type: Enum 'DIALOG' or 'HISTORY' to distingue between normal dialog message entries and messages for historisation of contribution
|
||||
|
||||
updated_at: the point of time the messages was updated
|
||||
|
||||
presented_at: the point of time the message was read for presenting in frontend
|
||||
|
After Width: | Height: | Size: 110 KiB |
@ -0,0 +1,57 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="3Q0Z80-KxArOo_UvcNgm" name="Seite-1">
|
||||
<mxGraphModel dx="1176" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="2336" pageHeight="1654" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="9" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontSize=16;startArrow=none;startFill=0;endArrow=classic;endFill=1;" edge="1" parent="1" source="2" target="6">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontSize=16;startArrow=none;startFill=0;endArrow=classic;endFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="2" target="7">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" style="edgeStyle=none;html=1;entryX=0;entryY=0;entryDx=0;entryDy=0;fontSize=16;startArrow=none;startFill=0;endArrow=classic;endFill=1;exitX=0.967;exitY=0.725;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="1" source="2" target="8">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="2" value="<font style="font-size: 16px">pending</font>" style="ellipse;whiteSpace=wrap;html=1;fillColor=#333333;fontColor=#FFFFFF;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="200" width="120" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="4" style="edgeStyle=none;html=1;entryX=0;entryY=1;entryDx=0;entryDy=0;fontSize=16;exitX=0;exitY=0.5;exitDx=0;exitDy=0;startArrow=classic;startFill=1;endArrow=none;endFill=0;" edge="1" parent="1" source="3" target="2">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="240" y="360"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="5" style="edgeStyle=none;html=1;entryX=1;entryY=1;entryDx=0;entryDy=0;fontSize=16;exitX=0;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="3" target="2">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="340" y="320"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="12" style="edgeStyle=none;html=1;entryX=0;entryY=1;entryDx=0;entryDy=0;fontSize=16;startArrow=none;startFill=0;endArrow=classic;endFill=1;exitX=0.942;exitY=0.288;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="1" source="3" target="6">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" style="edgeStyle=none;html=1;entryX=0;entryY=1;entryDx=0;entryDy=0;fontSize=16;startArrow=none;startFill=0;endArrow=classic;endFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="3" target="7">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontSize=16;startArrow=none;startFill=0;endArrow=classic;endFill=1;" edge="1" parent="1" source="3" target="8">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="<font style="font-size: 16px">in progress</font>" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="360" width="120" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="6" value="<font style="font-size: 16px">confirmed</font>" style="ellipse;whiteSpace=wrap;html=1;fillColor=#f5f5f5;gradientColor=#b3b3b3;strokeColor=#666666;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="160" width="120" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="<font style="font-size: 16px">denied</font>" style="ellipse;whiteSpace=wrap;html=1;fillColor=#f5f5f5;gradientColor=#b3b3b3;strokeColor=#666666;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="280" width="120" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8" value="<font style="font-size: 16px">deleted</font>" style="ellipse;whiteSpace=wrap;html=1;fillColor=#f5f5f5;gradientColor=#b3b3b3;strokeColor=#666666;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="400" width="120" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
|
After Width: | Height: | Size: 137 KiB |
BIN
docu/Concepts/BusinessRequirements/image/ContributionMyList.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 169 KiB |
BIN
docu/Concepts/BusinessRequirements/image/ContributionStates.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 35 KiB |
@ -47,7 +47,7 @@ describe('GddSend confirm', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('has total balance equal 0', () => {
|
||||
describe('has totalBalance under 0', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setProps({
|
||||
balance: 0,
|
||||
|
||||
@ -80,11 +80,33 @@ describe('LanguageSwitch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "fr-FR" (not supported)', () => {
|
||||
describe('navigator language is "fr-FR"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows French as language ', async () => {
|
||||
languageGetter.mockReturnValue('fr-FR')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Français - fr')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "nl-NL"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows Dutch as language ', async () => {
|
||||
languageGetter.mockReturnValue('nl-NL')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Holandés - nl')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "it-IT" (not supported)', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows English as language ', async () => {
|
||||
languageGetter.mockReturnValue('fr-FR')
|
||||
languageGetter.mockReturnValue('it-IT')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('English - en')
|
||||
@ -121,9 +143,27 @@ describe('LanguageSwitch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('language "fr" in store', () => {
|
||||
it('shows French as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'fr'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Français - fr')
|
||||
})
|
||||
})
|
||||
|
||||
describe('language "nl" in store', () => {
|
||||
it('shows Dutch as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'nl'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Holandés - nl')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dropdown menu', () => {
|
||||
it('has English and German as languages to choose', () => {
|
||||
expect(wrapper.findAll('li')).toHaveLength(3)
|
||||
expect(wrapper.findAll('li')).toHaveLength(5)
|
||||
})
|
||||
|
||||
it('has English as first language to choose', () => {
|
||||
@ -137,6 +177,14 @@ describe('LanguageSwitch', () => {
|
||||
it('has Español as second language to choose', () => {
|
||||
expect(wrapper.findAll('li').at(2).text()).toBe('Español')
|
||||
})
|
||||
|
||||
it('has French as second language to choose', () => {
|
||||
expect(wrapper.findAll('li').at(3).text()).toBe('Français')
|
||||
})
|
||||
|
||||
it('has Dutch as second language to choose', () => {
|
||||
expect(wrapper.findAll('li').at(4).text()).toBe('Holandés')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -162,6 +210,39 @@ describe('LanguageSwitch', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("with locale 'es'", () => {
|
||||
wrapper.findAll('li').at(2).find('a').trigger('click')
|
||||
expect(updateUserInfosMutationMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
locale: 'es',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("with locale 'fr'", () => {
|
||||
wrapper.findAll('li').at(3).find('a').trigger('click')
|
||||
expect(updateUserInfosMutationMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
locale: 'fr',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("with locale 'nl'", () => {
|
||||
wrapper.findAll('li').at(4).find('a').trigger('click')
|
||||
expect(updateUserInfosMutationMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
locale: 'nl',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -75,10 +75,28 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español')
|
||||
})
|
||||
})
|
||||
describe('navigator language is "fr-FR" (not supported)', () => {
|
||||
describe('navigator language is "fr-FR"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
it('shows French as language ', async () => {
|
||||
languageGetter.mockReturnValue('fr-FR')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(3).text()).toBe('Français')
|
||||
})
|
||||
})
|
||||
describe('navigator language is "nl-NL"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
it('shows Dutch as language ', async () => {
|
||||
languageGetter.mockReturnValue('nl-NL')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(4).text()).toBe('Holandés')
|
||||
})
|
||||
})
|
||||
describe('navigator language is "it-IT" (not supported)', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
it('shows English as language ', async () => {
|
||||
languageGetter.mockReturnValue('fr-FR')
|
||||
languageGetter.mockReturnValue('it-IT')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(0).text()).toBe('English')
|
||||
@ -99,7 +117,7 @@ describe('LanguageSwitch', () => {
|
||||
wrapper.vm.$store.state.language = 'de'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch')
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('English')
|
||||
})
|
||||
})
|
||||
describe('language "es" in store', () => {
|
||||
@ -107,47 +125,54 @@ describe('LanguageSwitch', () => {
|
||||
wrapper.vm.$store.state.language = 'es'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español')
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Deutsch')
|
||||
})
|
||||
})
|
||||
describe('language "fr" in store', () => {
|
||||
it('shows French as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'fr'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(3).text()).toBe('Español')
|
||||
})
|
||||
})
|
||||
describe('language "nl" in store', () => {
|
||||
it('shows Dutch as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'nl'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(4).text()).toBe('Français')
|
||||
})
|
||||
})
|
||||
describe('language menu', () => {
|
||||
it('has English, German and Español as languages to choose', () => {
|
||||
expect(wrapper.findAll('span.locales')).toHaveLength(3)
|
||||
expect(wrapper.findAll('span.locales')).toHaveLength(5)
|
||||
})
|
||||
it('has English as first language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(0).text()).toBe('English')
|
||||
expect(wrapper.findAll('span.locales').at(0).text()).toBe('Holandés')
|
||||
})
|
||||
it('has German as second language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch')
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('English')
|
||||
})
|
||||
it('has Español as third language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español')
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Deutsch')
|
||||
})
|
||||
it('has French as third language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(3).text()).toBe('Español')
|
||||
})
|
||||
it('has Dutch as third language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(4).text()).toBe('Français')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('calls the API', () => {
|
||||
it("with locale 'de'", () => {
|
||||
wrapper.findAll('span.locales').at(1).trigger('click')
|
||||
wrapper.findAll('span.locales').at(2).trigger('click')
|
||||
expect(updateUserInfosMutationMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
locale: 'de',
|
||||
},
|
||||
}),
|
||||
expect.objectContaining({ variables: { locale: 'de' } }),
|
||||
)
|
||||
})
|
||||
|
||||
// it("with locale 'en'", () => {
|
||||
// wrapper.findAll('span.locales').at(0).trigger('click')
|
||||
// expect(updateUserInfosMutationMock).toBeCalledWith(
|
||||
// expect.objectContaining({
|
||||
// variables: {
|
||||
// locale: 'en',
|
||||
// },
|
||||
// }),
|
||||
// )
|
||||
// })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,15 +1,37 @@
|
||||
<template>
|
||||
<div class="language-switch">
|
||||
<span
|
||||
v-for="(lang, index) in locales"
|
||||
@click.prevent="saveLocale(lang.code)"
|
||||
v-for="lang in locales"
|
||||
:key="lang.code"
|
||||
class="pointer pr-2"
|
||||
class="pointer"
|
||||
:class="$store.state.language === lang.code ? 'c-grey' : 'c-blau'"
|
||||
>
|
||||
<span class="locales">{{ lang.name }}</span>
|
||||
<span class="ml-3">{{ locales.length - 1 > index ? $t('math.pipe') : '' }}</span>
|
||||
<span v-if="lang.code === $store.state.language" class="locales mr-1">{{ lang.name }}</span>
|
||||
</span>
|
||||
<b-icon v-b-toggle.collapse-1 icon="caret-down-fill" aria-hidden="true"></b-icon>
|
||||
<b-collapse id="collapse-1" class="mt-4">
|
||||
<span
|
||||
v-for="(lang, index) in locales"
|
||||
@click.prevent="saveLocale(lang.code)"
|
||||
:key="lang.code"
|
||||
class="pointer"
|
||||
:class="$store.state.language === lang.code ? 'c-grey' : 'c-blau'"
|
||||
>
|
||||
<span v-if="lang.code !== $store.state.language" v-b-toggle.collapse-1 class="locales">
|
||||
{{ lang.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
lang.code !== $store.state.language &&
|
||||
(indexOfSelectedLocale !== indexOfLastLocale ||
|
||||
(indexOfSelectedLocale === indexOfLastLocale && index !== indexOfSecondLastLocale))
|
||||
"
|
||||
class="ml-3 mr-3"
|
||||
>
|
||||
{{ locales.length - 1 > index ? $t('math.pipe') : '' }}
|
||||
</span>
|
||||
</span>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -68,6 +90,17 @@ export default {
|
||||
this.currentLanguage = object
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
indexOfSelectedLocale() {
|
||||
return this.locales.findIndex((element) => element.code === this.$store.state.language)
|
||||
},
|
||||
indexOfSecondLastLocale() {
|
||||
return this.locales.length - 2
|
||||
},
|
||||
indexOfLastLocale() {
|
||||
return this.locales.length - 1
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.setCurrentLanguage()
|
||||
},
|
||||
|
||||
@ -17,6 +17,8 @@ export default {
|
||||
{ value: 'de', text: this.$t('settings.language.de') },
|
||||
{ value: 'en', text: this.$t('settings.language.en') },
|
||||
{ value: 'es', text: this.$t('settings.language.es') },
|
||||
{ value: 'fr', text: this.$t('settings.language.fr') },
|
||||
{ value: 'nl', text: this.$t('settings.language.nl') },
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
@ -48,53 +48,57 @@ describe('Navbar', () => {
|
||||
expect(wrapper.find('.navbar-toggler').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has twelve b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(12)
|
||||
it('has thirteen b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(13)
|
||||
})
|
||||
|
||||
it('has first nav-item "amount GDD" in navbar', () => {
|
||||
it('has nav-item "amount GDD" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('1234 GDD')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.overview" in navbar', () => {
|
||||
it('has nav-item "navigation.overview" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('navigation.overview')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.send" in navbar', () => {
|
||||
it('has nav-item "navigation.send" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(4).text()).toEqual('navigation.send')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.transactions" in navbar', () => {
|
||||
it('has nav-item "navigation.transactions" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.transactions')
|
||||
})
|
||||
|
||||
it('has first nav-item "gdt.gdt" in navbar', () => {
|
||||
it('has nav-item "gdt.gdt" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('gdt.gdt')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.community" in navbar', () => {
|
||||
it('has nav-item "navigation.community" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.community')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.profile" in navbar', () => {
|
||||
it('has nav-item "navigation.profile" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.profile')
|
||||
})
|
||||
|
||||
it('has nav-item "navigation.info" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.info')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigation Navbar (user has an elopage account)', () => {
|
||||
it('has a link to the members area', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(9).text()).toContain('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(9).find('a').attributes('href')).toBe(
|
||||
expect(wrapper.findAll('.nav-item').at(10).text()).toContain('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(10).find('a').attributes('href')).toBe(
|
||||
'https://elopage.com',
|
||||
)
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(10).text()).toEqual('navigation.admin_area')
|
||||
it('has nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(11).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(11).text()).toEqual('navigation.logout')
|
||||
it('has nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(12).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
|
||||
@ -104,12 +108,12 @@ describe('Navbar', () => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.admin_area')
|
||||
it('has nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(10).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(10).text()).toEqual('navigation.logout')
|
||||
it('has nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(11).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -64,6 +64,10 @@
|
||||
<b-icon icon="gear" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.profile') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item to="/information" class="mb-3">
|
||||
<b-icon icon="info-circle" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.info') }}
|
||||
</b-nav-item>
|
||||
<br />
|
||||
<b-nav-item v-if="$store.state.hasElopage" :href="elopageUri" class="mb-3" target="_blank">
|
||||
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
|
||||
|
||||
@ -32,48 +32,62 @@ describe('Sidebar', () => {
|
||||
expect(wrapper.find('div#component-sidebar').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('navigation Navbar (general elements)', () => {
|
||||
it('has first nav-item "navigation.overview" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(0).text()).toEqual('navigation.overview')
|
||||
describe('navigation Navbar', () => {
|
||||
it('has ten b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(10)
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.send" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('navigation.send')
|
||||
describe('navigation Navbar (general elements)', () => {
|
||||
it('has nav-item "navigation.overview" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(0).text()).toEqual('navigation.overview')
|
||||
})
|
||||
|
||||
it('has nav-item "navigation.send" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('navigation.send')
|
||||
})
|
||||
|
||||
it('has nav-item "gdt.gdt" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('gdt.gdt')
|
||||
})
|
||||
|
||||
it('has nav-item "navigation.community" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(4).text()).toContain('navigation.community')
|
||||
})
|
||||
|
||||
it('has nav-item "navigation.profile" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.profile')
|
||||
})
|
||||
|
||||
it('has nav-item "navigation.info" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.info')
|
||||
})
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.transactions" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(2).text()).toEqual('navigation.transactions')
|
||||
describe('navigation Navbar (user has an elopage account)', () => {
|
||||
it('has ten b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(10)
|
||||
})
|
||||
|
||||
it('has a link to the members area', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(7).find('a').attributes('href')).toBe('#')
|
||||
})
|
||||
|
||||
it('has nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
|
||||
it('has first nav-item "gdt.gdt" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('gdt.gdt')
|
||||
it('has nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.community" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(4).text()).toContain('navigation.community')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.profile" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.profile')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigation Navbar (user has an elopage account)', () => {
|
||||
it('has eight b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(9)
|
||||
})
|
||||
|
||||
it('has a link to the members area', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(6).find('a').attributes('href')).toBe('#')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.logout')
|
||||
it('has nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
|
||||
@ -83,16 +97,16 @@ describe('Sidebar', () => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has seven b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(8)
|
||||
it('has nine b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(9)
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.admin_area')
|
||||
it('has nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.logout')
|
||||
it('has nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -28,6 +28,10 @@
|
||||
<b-icon icon="gear" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.profile') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item to="/information" class="mb-3">
|
||||
<b-icon icon="info-circle" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.info') }}
|
||||
</b-nav-item>
|
||||
</b-nav>
|
||||
<hr />
|
||||
<b-nav vertical class="w-100">
|
||||
|
||||
@ -105,7 +105,8 @@ describe('TransactionLink', () => {
|
||||
'http://localhost/redeem/c00000000c000000c0000\n' +
|
||||
'Testy transaction-link.send_you 75 Gradido.\n' +
|
||||
'"Katzenauge, Eulenschrei, was verschwunden komm herbei!"\n' +
|
||||
'gdd_per_link.credit-your-gradido gdd_per_link.validUntilDate',
|
||||
'gdd_per_link.credit-your-gradido gdd_per_link.validUntilDate\n' +
|
||||
'gdd_per_link.link-hint',
|
||||
)
|
||||
})
|
||||
it('toasts success message', () => {
|
||||
|
||||
@ -163,6 +163,26 @@ export const listTransactionLinks = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const listContributionLinks = gql`
|
||||
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
||||
listContributionLinks(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
||||
links {
|
||||
id
|
||||
amount
|
||||
name
|
||||
memo
|
||||
createdAt
|
||||
validFrom
|
||||
validTo
|
||||
maxAmountPerMonth
|
||||
cycle
|
||||
maxPerCycle
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const listContributions = gql`
|
||||
query(
|
||||
$currentPage: Int = 1
|
||||
@ -209,3 +229,29 @@ export const listAllContributions = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const communityStatistics = gql`
|
||||
query {
|
||||
communityStatistics {
|
||||
totalUsers
|
||||
activeUsers
|
||||
deletedUsers
|
||||
totalGradidoCreated
|
||||
totalGradidoDecayed
|
||||
totalGradidoAvailable
|
||||
totalGradidoUnbookedDecayed
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const searchAdminUsers = gql`
|
||||
query {
|
||||
searchAdminUsers {
|
||||
userCount
|
||||
userList {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
|
||||
import en from 'vee-validate/dist/locale/en'
|
||||
import de from 'vee-validate/dist/locale/de'
|
||||
import es from 'vee-validate/dist/locale/es'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
function loadLocaleMessages() {
|
||||
@ -14,24 +10,9 @@ function loadLocaleMessages() {
|
||||
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
|
||||
if (matched && matched.length > 1) {
|
||||
const locale = matched[1]
|
||||
messages[locale] = locales(key)
|
||||
if (locale === 'de') {
|
||||
messages[locale] = {
|
||||
validations: de,
|
||||
...messages[locale],
|
||||
}
|
||||
}
|
||||
if (locale === 'en') {
|
||||
messages[locale] = {
|
||||
validations: en,
|
||||
...messages[locale],
|
||||
}
|
||||
}
|
||||
if (locale === 'es') {
|
||||
messages[locale] = {
|
||||
validations: es,
|
||||
...messages[locale],
|
||||
}
|
||||
messages[locale] = {
|
||||
validations: require(`vee-validate/dist/locale/${locale}`),
|
||||
...locales(key),
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -78,6 +59,32 @@ const numberFormats = {
|
||||
useGrouping: false,
|
||||
},
|
||||
},
|
||||
fr: {
|
||||
decimal: {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
},
|
||||
ungroupedDecimal: {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
useGrouping: false,
|
||||
},
|
||||
},
|
||||
nl: {
|
||||
decimal: {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
},
|
||||
ungroupedDecimal: {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
useGrouping: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const dateTimeFormats = {
|
||||
@ -165,6 +172,62 @@ const dateTimeFormats = {
|
||||
year: 'numeric',
|
||||
},
|
||||
},
|
||||
fr: {
|
||||
short: {
|
||||
day: 'numeric',
|
||||
month: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
long: {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
weekday: 'long',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
},
|
||||
monthShort: {
|
||||
month: 'short',
|
||||
},
|
||||
month: {
|
||||
month: 'long',
|
||||
},
|
||||
year: {
|
||||
year: 'numeric',
|
||||
},
|
||||
monthAndYear: {
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
},
|
||||
},
|
||||
nl: {
|
||||
short: {
|
||||
day: 'numeric',
|
||||
month: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
long: {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
weekday: 'long',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
},
|
||||
monthShort: {
|
||||
month: 'short',
|
||||
},
|
||||
month: {
|
||||
month: 'long',
|
||||
},
|
||||
year: {
|
||||
year: 'numeric',
|
||||
},
|
||||
monthAndYear: {
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default new VueI18n({
|
||||
|
||||
@ -26,8 +26,13 @@
|
||||
"community": "Gemeinschaft",
|
||||
"continue-to-registration": "Weiter zur Registrierung",
|
||||
"current-community": "Aktuelle Gemeinschaft",
|
||||
"members": "Mitglieder",
|
||||
"moderators": "Moderatoren",
|
||||
"myContributions": "Meine Beiträge zum Gemeinwohl",
|
||||
"openContributionLinks": "öffentliche Beitrags-Linkliste",
|
||||
"openContributionLinkText": "Folgende {count} automatische Schöpfungen werden zur Zeit durch die Gemeinschaft „{name}“ bereitgestellt.",
|
||||
"other-communities": "Weitere Gemeinschaften",
|
||||
"statistic": "Statistik",
|
||||
"submitContribution": "Beitrag einreichen",
|
||||
"switch-to-this-community": "zu dieser Gemeinschaft wechseln"
|
||||
},
|
||||
@ -59,7 +64,8 @@
|
||||
"yourActivity": "Bitte trage eine Tätigkeit ein!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "dankt dir mit"
|
||||
"thanksYouWith": "dankt dir mit",
|
||||
"unique": "(einmalig)"
|
||||
},
|
||||
"decay": {
|
||||
"before_startblock_transaction": "Diese Transaktion beinhaltet keine Vergänglichkeit.",
|
||||
@ -168,6 +174,7 @@
|
||||
"link-copied": "Link wurde in die Zwischenablage kopiert. Du kannst ihn jetzt in eine E-Mail oder Nachricht einfügen.",
|
||||
"link-deleted": "Der Link wurde am {date} gelöscht.",
|
||||
"link-expired": "Der Link ist nicht mehr gültig. Die Gültigkeit ist am {date} abgelaufen.",
|
||||
"link-hint": "Achtung: Jeder kann diesen Link einlösen. Gib ihn bitte nicht weiter!",
|
||||
"link-overview": "Linkübersicht",
|
||||
"links_count": "Aktive Links",
|
||||
"links_sum": "Offene Links und QR-Codes",
|
||||
@ -224,6 +231,7 @@
|
||||
"navigation": {
|
||||
"admin_area": "Adminbereich",
|
||||
"community": "Gemeinschaft",
|
||||
"info": "Information",
|
||||
"logout": "Abmelden",
|
||||
"members_area": "Mitgliederbereich",
|
||||
"overview": "Übersicht",
|
||||
@ -247,6 +255,8 @@
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"nl": "Dutch",
|
||||
"success": "Deine Sprache wurde erfolgreich geändert."
|
||||
},
|
||||
"name": {
|
||||
@ -298,6 +308,14 @@
|
||||
"uppercase": "Großbuchstabe erforderlich."
|
||||
}
|
||||
},
|
||||
"statistic": {
|
||||
"activeUsers": "Aktive Mitglieder",
|
||||
"deletedUsers": "Gelöschte Mitglieder",
|
||||
"totalGradidoAvailable": "GDD insgesamt im Umlauf",
|
||||
"totalGradidoCreated": "GDD insgesamt geschöpft",
|
||||
"totalGradidoDecayed": "GDD insgesamt verfallen",
|
||||
"totalGradidoUnbookedDecayed": "Gesamter ungebuchter GDD Verfall"
|
||||
},
|
||||
"success": "Erfolg",
|
||||
"time": {
|
||||
"days": "Tage",
|
||||
|
||||
@ -26,8 +26,13 @@
|
||||
"community": "Community",
|
||||
"continue-to-registration": "Continue to registration",
|
||||
"current-community": "Current community",
|
||||
"members": "Members",
|
||||
"moderators": "Moderators",
|
||||
"myContributions": "My contributions to the common good",
|
||||
"openContributionLinks": "open Contribution links list",
|
||||
"openContributionLinkText": "The following {count} automatic creations are currently provided by the \"{name}\" community.",
|
||||
"other-communities": "Other communities",
|
||||
"statistic": "Statistics",
|
||||
"submitContribution": "Submit contribution",
|
||||
"switch-to-this-community": "Switch to this community"
|
||||
},
|
||||
@ -59,7 +64,8 @@
|
||||
"yourActivity": "Please enter an activity!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "thanks you with"
|
||||
"thanksYouWith": "thanks you with",
|
||||
"unique": "(unique)"
|
||||
},
|
||||
"decay": {
|
||||
"before_startblock_transaction": "This transaction does not include decay.",
|
||||
@ -168,6 +174,7 @@
|
||||
"link-copied": "Link has been copied to the clipboard. You can now paste it into an email or message.",
|
||||
"link-deleted": "The link was deleted on {date}.",
|
||||
"link-expired": "The link is no longer valid. The validity expired on {date}.",
|
||||
"link-hint": "Attention: Anyone can redeem this link. Please do not share it!",
|
||||
"link-overview": "Link overview",
|
||||
"links_count": "Active links",
|
||||
"links_sum": "Open links and QR codes",
|
||||
@ -224,6 +231,7 @@
|
||||
"navigation": {
|
||||
"admin_area": "Admin Area",
|
||||
"community": "Community",
|
||||
"info": "Information",
|
||||
"logout": "Logout",
|
||||
"members_area": "Members area",
|
||||
"overview": "Overview",
|
||||
@ -247,6 +255,8 @@
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"nl": "Holandés",
|
||||
"success": "Your language has been successfully updated."
|
||||
},
|
||||
"name": {
|
||||
@ -298,6 +308,14 @@
|
||||
"uppercase": "One uppercase letter required."
|
||||
}
|
||||
},
|
||||
"statistic": {
|
||||
"activeUsers": "Active members",
|
||||
"deletedUsers": "Deleted members",
|
||||
"totalGradidoAvailable": "Total GDD in circulation",
|
||||
"totalGradidoCreated": "Total created GDD",
|
||||
"totalGradidoDecayed": "Total GDD decay",
|
||||
"totalGradidoUnbookedDecayed": "Total unbooked GDD decay"
|
||||
},
|
||||
"success": "Success",
|
||||
"time": {
|
||||
"days": "Days",
|
||||
|
||||
@ -26,8 +26,13 @@
|
||||
"community": "Comunidad",
|
||||
"continue-to-registration": "Continuar con el registro",
|
||||
"current-community": "Comunidad actual",
|
||||
"members": "Miembros",
|
||||
"moderators": "Moderadores",
|
||||
"myContributions": "Mis contribuciones al bien común",
|
||||
"openContributionLinks": "lista de enlaces de contribuciones públicas",
|
||||
"openContributionLinkText": "La comunidad \"{name}\" proporciona actualmente las siguientes {count} creaciones automáticas.",
|
||||
"other-communities": "Otras comunidades",
|
||||
"statistic": "Estadísticas",
|
||||
"submitContribution": "Aportar una contribución",
|
||||
"switch-to-this-community": "cambiar a esta comunidad"
|
||||
},
|
||||
@ -59,7 +64,8 @@
|
||||
"yourActivity": "¡Por favor, introduce una actividad!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "agradecidos con"
|
||||
"thanksYouWith": "te agradece con",
|
||||
"unique": "(único)"
|
||||
},
|
||||
"decay": {
|
||||
"before_startblock_transaction": "Esta transacción no implica disminución en su valor.",
|
||||
@ -168,6 +174,7 @@
|
||||
"link-copied": "El enlace se ha copiado en el portapapeles. Ahora puedes pegarlo en un correo electrónico o mensaje.",
|
||||
"link-deleted": "El enlace se eliminó el {date}.",
|
||||
"link-expired": "El enlace ya no es válido. La validez expiró el {date}.",
|
||||
"link-hint": "Atención: cualquiera puede canjear este enlace. Por favor, no lo transmitan.",
|
||||
"link-overview": "Resumen de enlaces",
|
||||
"links_count": "Enlaces activos",
|
||||
"links_sum": "Enlaces abiertos y códigos QR",
|
||||
@ -224,6 +231,7 @@
|
||||
"navigation": {
|
||||
"admin_area": "Área de administración",
|
||||
"community": "Comunidad",
|
||||
"info": "Información",
|
||||
"logout": "Salir",
|
||||
"members_area": "Área de afiliados",
|
||||
"overview": "Resumen",
|
||||
@ -247,6 +255,8 @@
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Francés",
|
||||
"nl": "Holandés",
|
||||
"success": "Tu idioma ha sido cambiado con éxito."
|
||||
},
|
||||
"name": {
|
||||
@ -298,6 +308,14 @@
|
||||
"uppercase": "Letra mayúscula requerida."
|
||||
}
|
||||
},
|
||||
"statistic": {
|
||||
"activeUsers": "miembros activos",
|
||||
"deletedUsers": "miembros eliminados",
|
||||
"totalGradidoAvailable": "GDD total en circulación",
|
||||
"totalGradidoCreated": "GDD total creado",
|
||||
"totalGradidoDecayed": "GDD total decaído",
|
||||
"totalGradidoUnbookedDecayed": "GDD no contabilizado decaído"
|
||||
},
|
||||
"success": "Lo lograste",
|
||||
"time": {
|
||||
"days": "Días",
|
||||
|
||||
342
frontend/src/locales/fr.json
Normal file
@ -0,0 +1,342 @@
|
||||
{
|
||||
"100": "100%",
|
||||
"1000thanks": "1000 mercis d'être avec nous!",
|
||||
"125": "125%",
|
||||
"85": "85%",
|
||||
"advanced-calculation": "Calcul avancé",
|
||||
"auth": {
|
||||
"left": {
|
||||
"dignity": "Dignité",
|
||||
"donation": "Donation",
|
||||
"gratitude": "Gratitude",
|
||||
"hasAccount": "Avez-vous déjà un compte?",
|
||||
"hereLogin": "Connectez-vous ici",
|
||||
"learnMore": "Pour plus de détails …",
|
||||
"oneDignity": "Nous nous donnons mutuellement et rendons grâce avec Gradido.",
|
||||
"oneDonation": "Vous êtes précieux pour la communauté. 1000 mercis d’être parmi nous.",
|
||||
"oneGratitude": "Les uns pour les autres, pour tout le monde, pour la nature."
|
||||
},
|
||||
"navbar": {
|
||||
"aboutGradido": "À propos de Gradido"
|
||||
}
|
||||
},
|
||||
"back": "Retour",
|
||||
"community": {
|
||||
"choose-another-community": "Choisissez une autre communauté",
|
||||
"community": "Communauté",
|
||||
"continue-to-registration": "Continuez l´inscription",
|
||||
"current-community": "Communauté actuelle",
|
||||
"members": "Membres",
|
||||
"moderators": "Modérateurs",
|
||||
"myContributions": "Mes contributions aux biens communs",
|
||||
"openContributionLinks": "liste de liens de contribution publique",
|
||||
"openContributionLinkText": "Les {count} créations automatiques suivantes sont actuellement fournies par la communauté \"{name}\".",
|
||||
"other-communities": "Autres communautés",
|
||||
"statistic": "Statistiques",
|
||||
"submitContribution": "Donner une contribution",
|
||||
"switch-to-this-community": "Passer à cette communauté"
|
||||
},
|
||||
"contribution": {
|
||||
"activity": "Activité",
|
||||
"alert": {
|
||||
"communityNoteList": "Vous trouverez ci-contre toutes les contributions versées et certifiées de tous les membres de cette communauté.",
|
||||
"confirm": " Approuvé",
|
||||
"myContributionNoteList": "À tout moment vous pouvez éditer ou supprimer les données qui n´ont pas été confirmées.",
|
||||
"myContributionNoteSupport": "Vous aurez bientôt la possibilité de dialoguer avec un médiateur. Si vous rencontrez un problème maintenant, merci de contacter l´aide en ligne.",
|
||||
"pending": "Inscription en attente de validation",
|
||||
"rejected": "supprimé"
|
||||
},
|
||||
"date": "Contribution pour:",
|
||||
"delete": "Supprimer la contribution! Êtes-vous sûr?",
|
||||
"deleted": "La contribution a été supprimée! Mais elle restera visible.",
|
||||
"formText": {
|
||||
"bringYourTalentsTo": "Apportez vos talents à la communauté! Votre participation bénévole sera récompensée de 20 GDD/heure jusqu´à un plafond de 1000 GDD/mois.",
|
||||
"describeYourCommunity": "Décrivez votre activité/service à la communauté en mentionnant le nombre d´heures, et calculez le montant à raison de 20 GDD/heure! Après confirmation par l´un de nos collaborateurs, le montant sera crédité sur votre compte.",
|
||||
"maxGDDforMonth": "Vous pouvez seulement déclarer un montant maximum de (montant) GDD pour le mois sélectionné.",
|
||||
"openAmountForMonth": "Pour <b>{monthAndYear}</b>, vous pouvez encore déclarer <b>{creation}</b> GDD.",
|
||||
"yourContribution": "Votre contribution au bien commun"
|
||||
},
|
||||
"noDateSelected": "Choisissez n´importe quelle date du mois",
|
||||
"selectDate": "Quand a été effectuée votre contribution?",
|
||||
"submit": "Soumettre",
|
||||
"submitted": "La contribution a été soumise.",
|
||||
"updated": "La contribution a été modifiée.",
|
||||
"yourActivity": "Veuillez saisir une activité!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "vous remercie avec",
|
||||
"unique": "(unique)"
|
||||
},
|
||||
"decay": {
|
||||
"before_startblock_transaction": "Cette transaction n´est pas péremptoire.",
|
||||
"calculation_decay": "Calcul de la décroissance",
|
||||
"calculation_total": "Calcul du montant total",
|
||||
"decay": "Décroissance",
|
||||
"decay_introduced": "Décroissance engagée le:",
|
||||
"decay_since_last_transaction": "Décroissance depuis la dernière transaction",
|
||||
"last_transaction": "Dernière transaction:",
|
||||
"past_time": "Le temps a expiré",
|
||||
"Starting_block_decay": "Début de la décroissance",
|
||||
"total": "Total",
|
||||
"types": {
|
||||
"creation": "Créé",
|
||||
"noDecay": "Aucun déclin",
|
||||
"receive": "Reçu",
|
||||
"send": "Envoyé"
|
||||
}
|
||||
},
|
||||
"delete": "Supprimer",
|
||||
"em-dash": "—",
|
||||
"error": {
|
||||
"email-already-sent": "Nous vous avons déjà envoyé un email il y a moins de 10 minutes.",
|
||||
"empty-transactionlist": "Il y a eu une erreur lors de la transmission du numéro de votre transaction.",
|
||||
"error": "Erreur!",
|
||||
"no-account": "Malheureusement nous n´avons pas pu trouver de compte (actif) correspondant aux données transmises.",
|
||||
"no-transactionlist": "Il y a malheureusement eu une erreur. Aucune transaction n´a été envoyée depuis l`serveur.",
|
||||
"no-user": "Pas d`utilisateur pour cet identifiant.",
|
||||
"session-expired": "La session a expiré pour des raisons de sécurité.",
|
||||
"unknown-error": "Erreur inconnue: "
|
||||
},
|
||||
"followUs": "Suivez-nous:",
|
||||
"footer": {
|
||||
"app_version": "Version de l'application {version}",
|
||||
"copyright": {
|
||||
"link": "Académie Gradido",
|
||||
"year": "© {year}"
|
||||
},
|
||||
"imprint": "Notification légale",
|
||||
"privacy_policy": "Politique de confidentialité",
|
||||
"short_hash": "({shortHash})",
|
||||
"whitepaper": "Papier blanc"
|
||||
},
|
||||
"form": {
|
||||
"amount": "Montant",
|
||||
"at": "à",
|
||||
"cancel": "Annuler",
|
||||
"change": "Changer",
|
||||
"check_now": "Vérifier maintenant",
|
||||
"close": "Fermer",
|
||||
"current_balance": "Solde actuel",
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
"email": "Email",
|
||||
"firstname": " Prénom",
|
||||
"from": "de",
|
||||
"generate_now": "Produire maintenant",
|
||||
"lastname": "Nom",
|
||||
"mandatoryField": "champ obligatoire",
|
||||
"memo": "Note",
|
||||
"message": "Message",
|
||||
"new_balance": "Montant du solde après confirmation",
|
||||
"no_gdd_available": "Vous n´avez pas de GDD à envoyer.",
|
||||
"password": "Mot de passe",
|
||||
"passwordRepeat": "Répétez le mot de passe",
|
||||
"password_new": "Nouveau mot de passe",
|
||||
"password_new_repeat": "Répétez le nouveau mot de passe",
|
||||
"password_old": "Ancien mot de passe",
|
||||
"recipient": "Destinataire",
|
||||
"reset": "Réinitialiser",
|
||||
"save": "Sauvegarder",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scannez le QR code de votre partenaire",
|
||||
"sender": "Expéditeur",
|
||||
"send_check": "Confirmez la transaction. Veuillez revérifier toutes les données svp!",
|
||||
"send_now": "Envoyez maintenant",
|
||||
"send_transaction_error": "Malheureusement, la transaction n´a pas pu être effectuée!",
|
||||
"send_transaction_success": "Votre transaction a été effectuée avec succès",
|
||||
"sorry": "Désolé",
|
||||
"thx": "Merci",
|
||||
"time": "Heure",
|
||||
"to": "à",
|
||||
"to1": "à",
|
||||
"validation": {
|
||||
"gddSendAmount": "L´espace {_field_} doit comprendre un nombre entre {min} et {max} avec un maximum de deux chiffres après la virgule",
|
||||
"is-not": "Vous ne pouvez pas vous envoyer de Gradido à vous-même",
|
||||
"usernmae-regex": "Le nom d´utilisateur doit commencer par une lettre, suivi d´au moins deux caractères alphanumériques.",
|
||||
"usernmae-unique": "Ce nom d´utilisateur est déjà pris."
|
||||
},
|
||||
"your_amount": "Votre montant"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"gdd_per_link": {
|
||||
"choose-amount": "Sélectionnez le montant que vous souhaitez envoyer via lien. Vous pouvez également joindre un message. Cliquez sur ‘créer maintenant’ pour établir un lien que vous pourrez partager.",
|
||||
"copy-link": "Copier le lien",
|
||||
"copy-link-with-text": "Copier le lien et le texte",
|
||||
"created": "Le lien a été créé!",
|
||||
"credit-your-gradido": "Pour l´accréditation du Gradido, cliquer sur le lien!",
|
||||
"decay-14-day": "Perte sur 14 jours",
|
||||
"delete-the-link": "Supprimer le lien?",
|
||||
"deleted": "Le lien a été supprimé!",
|
||||
"expiredOn": "A expiré le",
|
||||
"has-account": "Vous avez déjà un compte Gradido?",
|
||||
"header": "Envoyer des Gradidos via lien",
|
||||
"isFree": "Gradido est gratuit mondialement.",
|
||||
"link-and-text-copied": "Le lien et votre message ont été copiés dans le presse-papier. Vous pouvez maintenant le joindre à un email ou à un message..",
|
||||
"link-copied": "Le lien a été copié dans le presse-papier. Vous pouvez désormais le coller dans votre email ou votre message.",
|
||||
"link-deleted": "Le lien a été supprimé le on {date}.",
|
||||
"link-expired": "Le lien n´est plus valide. Sa validité a expiré le {date}.",
|
||||
"link-hint": "Attention : tout le monde peut utiliser ce lien. Ne le partage pas, s'il te plaît!",
|
||||
"link-overview": "Aperçu du lien",
|
||||
"links_count": "Liens actifs",
|
||||
"links_sum": "Ouvrir les liens et les QR codes",
|
||||
"no-account": "Vous n´avez pas encore de compte Gradido?",
|
||||
"no-redeem": "Vous n´êtes pas autorisé à percevoir votre propre lien!",
|
||||
"not-copied": "Malheureusement votre appareil ne permet pas de copier! Veuillez copier le lien manuellement svp!",
|
||||
"redeem": "Encaisser",
|
||||
"redeem-text": "Voulez-vous percevoir le montant maintenant?",
|
||||
"redeemed": "Encaissé avec succès! Votre compte est crédité de {n} GDD.",
|
||||
"redeemed-at": "Le lien a déjà été perçu le {date}.",
|
||||
"redeemed-title": "encaisser",
|
||||
"to-login": "Connexion",
|
||||
"to-register": "Enregistrer un nouveau compte.",
|
||||
"validUntil": "Valide jusqu´au",
|
||||
"validUntilDate": "Le lien est valide jusqu´au {date}."
|
||||
},
|
||||
"gdt": {
|
||||
"calculation": "Calcul de Gradido Transform",
|
||||
"contribution": "Contribution",
|
||||
"conversion": "Conversion",
|
||||
"conversion-gdt-euro": "Conversion Euro / Gradido Transform (GDT)",
|
||||
"credit": "Crédit",
|
||||
"factor": "Coefficient",
|
||||
"formula": "Formule de calcul",
|
||||
"funding": "Aux contributions au financement",
|
||||
"gdt": "Gradido Transform",
|
||||
"gdt-received": "Gradido Transform (GDT) perçu",
|
||||
"no-transactions": "Vous ne possédez pas encore Gradido Transform (GDT).",
|
||||
"not-reachable": "Le Serveur GDT n´est pas accessible.",
|
||||
"publisher": "Un membre que vous avez référé a apporté un contribution",
|
||||
"raise": "Augmentation",
|
||||
"recruited-member": "Membre invité"
|
||||
},
|
||||
"language": "Langage",
|
||||
"link-load": "Enregistrer le dernier lien | Enregistrer les derniers {n} liens | Enregistrer plus de {n} liens",
|
||||
"login": "Connexion",
|
||||
"math": {
|
||||
"aprox": "~",
|
||||
"asterisk": "*",
|
||||
"equal": "=",
|
||||
"minus": "−",
|
||||
"pipe": "|"
|
||||
},
|
||||
"message": {
|
||||
"activateEmail": "Votre compte n´a pas encore été activé. Veuillez vérifier vos emails et cliquer sur le lien d´activation ou faites la demande d´un nouveau lien en utilisant la page qui permet de générer un nouveau mot de passe.",
|
||||
"checkEmail": "Votre email a bien été vérifié. Vous pouvez vous enregistrer maintenant.",
|
||||
"email": "Nous vous avons envoyé un email.",
|
||||
"errorTitle": "Attention!",
|
||||
"register": "Vous êtes enregistré maintenant, merci de vérifier votre boîte mail et cliquer sur le lien d´activation.",
|
||||
"reset": "Votre mot de passe a été modifié.",
|
||||
"title": "Merci!",
|
||||
"unsetPassword": "Votre mot de passe n´a pas été accepté. Merci de le réinitialiser."
|
||||
},
|
||||
"navigation": {
|
||||
"admin_area": "Partie administrative",
|
||||
"community": "Communauté",
|
||||
"info": "Information",
|
||||
"logout": "Déconnexion",
|
||||
"members_area": "Partie réservée aux membres",
|
||||
"overview": "Aperçu",
|
||||
"profile": "Mon profile",
|
||||
"send": "Envoyer",
|
||||
"support": "Aide",
|
||||
"transactions": "Transactions"
|
||||
},
|
||||
"qrCode": "QR Code",
|
||||
"send_gdd": "Envoyer GDD",
|
||||
"send_per_link": "Envoyer GDD via lien",
|
||||
"session": {
|
||||
"extend": "Rester connecter",
|
||||
"lightText": "S´il n´apparaît aucune activité pendant plus de 10 minutes, la session expirera pour des raisons de sécurité.",
|
||||
"logoutIn": "Se déconnecter ",
|
||||
"warningText": "Êtes-vous toujours connecté?"
|
||||
},
|
||||
"settings": {
|
||||
"language": {
|
||||
"changeLanguage": "Changer la langue",
|
||||
"de": "Allemand",
|
||||
"en": "Anglais",
|
||||
"es": "Espagnol",
|
||||
"fr": "Français",
|
||||
"nl": "Néerlandais",
|
||||
"success": "Votre langue de préférence a bien été actualisée."
|
||||
},
|
||||
"name": {
|
||||
"change-name": "Changer le nom",
|
||||
"change-success": "Votre nom a bien été changé."
|
||||
},
|
||||
"newsletter": {
|
||||
"newsletter": "Information par email",
|
||||
"newsletterFalse": "Vous ne recevrez aucune information par email.",
|
||||
"newsletterTrue": "Vous recevrez de l´information par email."
|
||||
},
|
||||
"password": {
|
||||
"change-password": "Changer le mot de passe",
|
||||
"forgot_pwd": "Mot de passe oublié?",
|
||||
"resend_subtitle": "Votre lien d´activation a expiré, vous pouvez en obtenir un nouveau ici.",
|
||||
"reset": "Réinitialiser le mot de passe",
|
||||
"reset-password": {
|
||||
"text": "Entrez un nouveau mot de passe que vous utiliserez dans le futur pour vous connecter à votre compte Gradido.."
|
||||
},
|
||||
"send_now": "Envoyer maintenant",
|
||||
"set": "Définir le mot de passe",
|
||||
"set-password": {
|
||||
"text": "Sauvegardez votre nouveau mot de passe maintenant, que vous pourrez utiliser pour vous connecter à votre compte Gradido dans le futur."
|
||||
},
|
||||
"subtitle": "Si vous avez oublié votre mot de passe, vous pouvez le réinitialiser ici."
|
||||
}
|
||||
},
|
||||
"signin": "S´identifier",
|
||||
"signup": "S´inscrire",
|
||||
"site": {
|
||||
"forgotPassword": {
|
||||
"heading": "Veuillez entrer l´adresse email sous laquelle vous êtes enregistré ici svp."
|
||||
},
|
||||
"login": {
|
||||
"heading": "Vous connecter avec vos données d´accès. Gardez les en sécurité!"
|
||||
},
|
||||
"resetPassword": {
|
||||
"heading": "Entrez votre mot de passe et répétez l´action svp."
|
||||
},
|
||||
"signup": {
|
||||
"agree": "J´accepte le <a href='https://gradido.net/en/datenschutz/' target='_blank' > politique de confidentialité </a>.",
|
||||
"dont_match": "Les mots de passe ne correspondent pas.",
|
||||
"heading": "Vous enregistrer en entrant toutes les données demandées dans les champs requis.",
|
||||
"lowercase": "Une lettre minuscule est requise.",
|
||||
"minimum": "8 caractères minimum.",
|
||||
"no-whitespace": "Pas d´espace ni d´onglet",
|
||||
"one_number": "Un chiffre requis.",
|
||||
"special-char": "Un caractère spécial requis (e.g. _ or ä)",
|
||||
"uppercase": "Une lettre majuscule requise."
|
||||
}
|
||||
},
|
||||
"statistic": {
|
||||
"activeUsers": "Membres actifs",
|
||||
"deletedUsers": "Membres supprimés",
|
||||
"totalGradidoAvailable": "GDD total en circulation",
|
||||
"totalGradidoCreated": "GDD total puisé",
|
||||
"totalGradidoDecayed": "Total de GDD écoulé",
|
||||
"totalGradidoUnbookedDecayed": "Total GDD non comptabilisé écoulé"
|
||||
},
|
||||
"success": "Avec succès",
|
||||
"time": {
|
||||
"days": "Jours",
|
||||
"hours": "Heures",
|
||||
"minutes": "Minutes",
|
||||
"month": "Moi",
|
||||
"months": "Mois",
|
||||
"seconds": "Secondes",
|
||||
"years": "Année"
|
||||
},
|
||||
"transaction": {
|
||||
"gdd-text": "Transactions Gradido",
|
||||
"gdt-text": "Transactions de GradidoTransform",
|
||||
"nullTransactions": "Vous n´avez pas encore de transaction effectuée sur votre compte.",
|
||||
"receiverDeleted": "Le compte du destinataire n´existe plus",
|
||||
"receiverNotFound": "Destinataire inconnu",
|
||||
"show_all": "Voir toutes les <strong>{count}</strong> transactions."
|
||||
},
|
||||
"transaction-link": {
|
||||
"send_you": "veut vous envoyer"
|
||||
},
|
||||
"via_link": "par lien",
|
||||
"welcome": "Bienvenu dans la communauté"
|
||||
}
|
||||
@ -17,6 +17,18 @@ const locales = [
|
||||
iso: 'es-ES',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: 'Français',
|
||||
code: 'fr',
|
||||
iso: 'fr-FR',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: 'Holandés',
|
||||
code: 'nl',
|
||||
iso: 'nl-NL',
|
||||
enabled: true,
|
||||
},
|
||||
]
|
||||
|
||||
export default locales
|
||||
|
||||
342
frontend/src/locales/nl.json
Normal file
@ -0,0 +1,342 @@
|
||||
{
|
||||
"100": "100%",
|
||||
"1000thanks": "1000 dank, omdat je bij ons bent!",
|
||||
"125": "125%",
|
||||
"85": "85%",
|
||||
"advanced-calculation": "Voorcalculatie",
|
||||
"auth": {
|
||||
"left": {
|
||||
"dignity": "Waardigheid",
|
||||
"donation": "Gift",
|
||||
"gratitude": "Dankbaarheid",
|
||||
"hasAccount": "Je hebt al een rekening?",
|
||||
"hereLogin": "Hier aanmelden",
|
||||
"learnMore": "Meer ervaren …",
|
||||
"oneDignity": "We geven aan elkaar en bedanken met Gradido.",
|
||||
"oneDonation": "Jij bent een geschenk voor de gemeenschap. 1000 dank dat je bij ons bent.",
|
||||
"oneGratitude": "Voor elkaar, voor alle mensen, voor de natuur."
|
||||
},
|
||||
"navbar": {
|
||||
"aboutGradido": "Over Gradido"
|
||||
}
|
||||
},
|
||||
"back": "Terug",
|
||||
"community": {
|
||||
"choose-another-community": "Kies een andere gemeenschap",
|
||||
"community": "Gemeenschap",
|
||||
"continue-to-registration": "Verder ter registratie",
|
||||
"current-community": "Actuele gemeenschap",
|
||||
"members": "Leden",
|
||||
"moderators": "Moderators",
|
||||
"myContributions": "Mijn bijdragen voor het algemeen belang",
|
||||
"openContributionLinks": "openbare lijst van bijdragen",
|
||||
"openContributionLinkText": "De volgende {count} automatische creaties worden momenteel aangeboden door de gemeenschap \"{name}\".",
|
||||
"other-communities": "Verdere gemeenschappen",
|
||||
"statistic": "Statistieken",
|
||||
"submitContribution": "Bijdrage indienen",
|
||||
"switch-to-this-community": "naar deze gemeenschap wisselen"
|
||||
},
|
||||
"contribution": {
|
||||
"activity": "Activiteit",
|
||||
"alert": {
|
||||
"communityNoteList": "Hier vind je alle ingediende en bevestigde bijdragen van alle leden uit deze gemeenschap.",
|
||||
"confirm": "bevestigt",
|
||||
"myContributionNoteList": "Ingediende bijdragen, die nog niet bevestigd zijn, kun je op elk moment wijzigen of verwijderen.",
|
||||
"myContributionNoteSupport": "Hier heb je binnenkort de mogelijkheid een gesprek met een moderator te voeren. Mocht je nu problemen hebben, dan neem alsjeblieft contact op met Support.",
|
||||
"pending": "Ingediend en wacht op bevestiging",
|
||||
"rejected": "afgewezen"
|
||||
},
|
||||
"date": "Bijdrage voor:",
|
||||
"delete": "Bijdrage verwijderen! Weet je het zeker?",
|
||||
"deleted": "De bijdrage werd verwijderd! Blijft echter zichtbaar.",
|
||||
"formText": {
|
||||
"bringYourTalentsTo": "Bied je met jouw talenten in de gemeenschap aan! Jouw vrijwillige inzet belonen we met 20 GGD per uur, tot maximaal 1.000 GGD per maand.",
|
||||
"describeYourCommunity": "Beschrijf jouw activiteit voor het algemeen belang met vermelding van het aantal uren en registreer een bedrag van 20 GDD per uur! Na bevestiging door een moderator wordt het bedrag op jouw rekening bijgeschreven.",
|
||||
"maxGDDforMonth": "Je kunt voor de geselecteerde maand nog maximaal {amount} GDD indienen.",
|
||||
"openAmountForMonth": "Voor <b>{monthAndYear}</b> kun je nog <b>{creation}</b> GDD indienen.",
|
||||
"yourContribution": "Jouw bijdrage voor het algemeen belang"
|
||||
},
|
||||
"noDateSelected": "Kies een willekeurige datum in de maand",
|
||||
"selectDate": "Wanneer was jouw bijdrage?",
|
||||
"submit": "Indienen",
|
||||
"submitted": "De bijdrage werd ingediend.",
|
||||
"updated": "De bijdrage werd veranderd.",
|
||||
"yourActivity": "Voer een activiteit in!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "bedankt jou met",
|
||||
"unique": "(uniek)"
|
||||
},
|
||||
"decay": {
|
||||
"before_startblock_transaction": "Deze transactie heeft geen vergankelijkheid.",
|
||||
"calculation_decay": "Berekening van de vergankelijkheid",
|
||||
"calculation_total": "Berekening van het totaalbedrag",
|
||||
"decay": "Vergankelijkheid",
|
||||
"decay_introduced": "De vergankelijkheid werd ingevoerd op:",
|
||||
"decay_since_last_transaction": "Vergankelijkheid sinds de laatste transactie",
|
||||
"last_transaction": "Laatste transactie",
|
||||
"past_time": "Verlopen tijd",
|
||||
"Starting_block_decay": "Startblok vergankelijkheid",
|
||||
"total": "Totaal",
|
||||
"types": {
|
||||
"creation": "Gecreëerd",
|
||||
"noDecay": "Geen vergankelijkheid",
|
||||
"receive": "Ontvangen",
|
||||
"send": "Verstuurd"
|
||||
}
|
||||
},
|
||||
"delete": "Verwijderen",
|
||||
"em-dash": "—",
|
||||
"error": {
|
||||
"email-already-sent": "Wij hebben jou minder dan 10 minuten geleden een email gestuurd.",
|
||||
"empty-transactionlist": "Er was een fout met de overdracht van het aantal van jouw transacties.",
|
||||
"error": "Fout!",
|
||||
"no-account": "Helaas kunnen we geen (geactiveerde) rekening met deze gegevens vinden.",
|
||||
"no-transactionlist": " Helaas was er een fout. Door de server werden er geen transacties overgedragen.",
|
||||
"no-user": "Geen gebruiker met deze login gegevens.",
|
||||
"session-expired": "De sessie werd om veiligheidsredenen beëindigd.",
|
||||
"unknown-error": "Onbekende fout: "
|
||||
},
|
||||
"followUs": "volg ons:",
|
||||
"footer": {
|
||||
"app_version": "app versie {version}",
|
||||
"copyright": {
|
||||
"link": "Gradido-Academie",
|
||||
"year": "© {year}"
|
||||
},
|
||||
"imprint": "Colofon",
|
||||
"privacy_policy": "Privacyverklaring",
|
||||
"short_hash": "({shortHash})",
|
||||
"whitepaper": "Witboek"
|
||||
},
|
||||
"form": {
|
||||
"amount": "Bedrag",
|
||||
"at": "op",
|
||||
"cancel": "Annuleren",
|
||||
"change": "Wijzigen",
|
||||
"check_now": "Nu controleren",
|
||||
"close": "Sluiten",
|
||||
"current_balance": "Actueel banksaldo",
|
||||
"date": "Datum",
|
||||
"description": "Beschrijving",
|
||||
"email": "Email",
|
||||
"firstname": "Voornaam",
|
||||
"from": "Van",
|
||||
"generate_now": "Nu genereren",
|
||||
"lastname": "Achternaam",
|
||||
"mandatoryField": "verplicht veld",
|
||||
"memo": "Memo",
|
||||
"message": "Bericht",
|
||||
"new_balance": "Nieuw banksaldo na bevestiging",
|
||||
"no_gdd_available": "Je hebt geen GDD om te versturen.",
|
||||
"password": "Wachtwoord",
|
||||
"passwordRepeat": "Wachtwoord herhalen",
|
||||
"password_new": "Nieuw wachtwoord",
|
||||
"password_new_repeat": "Nieuw wachtwoord herhalen",
|
||||
"password_old": "Oud wachtwoord",
|
||||
"recipient": "Ontvanger",
|
||||
"reset": "Resetten",
|
||||
"save": "Opslaan",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scan de QR Code van uw partner",
|
||||
"sender": "Afzender",
|
||||
"send_check": "Bevestig jouw transactie. Controleer alsjeblieft nogmaals alle gegevens!",
|
||||
"send_now": "Nu versturen",
|
||||
"send_transaction_error": "Helaas kon de transactie niet uitgevoerd worden!",
|
||||
"send_transaction_success": " Jouw transactie werd succesvol uitgevoerd ",
|
||||
"sorry": "Sorry",
|
||||
"thx": "Dankjewel",
|
||||
"time": "Tijd",
|
||||
"to": "tot",
|
||||
"to1": "aan",
|
||||
"validation": {
|
||||
"gddSendAmount": "Het veld {_field_} moet een getal tussen {min} en {max} met maximaal twee cijfers achter de komma zijn",
|
||||
"is-not": "Je kunt geen Gradidos aan jezelf overmaken",
|
||||
"usernmae-regex": "De gebruikersnaam moet met een letter beginnen, waarop minimaal twee alfanumerieke tekens dienen te volgen.",
|
||||
"usernmae-unique": "De gebruikersnaam is al bezet."
|
||||
},
|
||||
"your_amount": "Jouw bijdrage"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"gdd_per_link": {
|
||||
"choose-amount": "Kies een bedrag dat je per link versturen wil. Je kunt ook nog een bericht invullen. Wanneer je „Nu genereren“ klikt, wordt er een link gecreëerd die je kunt versturen.",
|
||||
"copy-link": "Link kopiëren",
|
||||
"copy-link-with-text": "Link en tekst kopiëren",
|
||||
"created": "De link werd gecreëerd!",
|
||||
"credit-your-gradido": "Om de Gradidos bijgeschreven te krijgen, klik op de link!",
|
||||
"decay-14-day": "Vergankelijkheid voor 14 dagen",
|
||||
"delete-the-link": "De link verwijderen?",
|
||||
"deleted": "De link werd verwijderd!",
|
||||
"expiredOn": "Afgelopen op",
|
||||
"has-account": "Heb je al een Gradido rekening?",
|
||||
"header": "Gradidos per link versturen",
|
||||
"isFree": "Gradido is gratis wereldwijd.",
|
||||
"link-and-text-copied": "De link en jouw bericht werden naar het klembord gekopieerd. Je kunt ze nu in een email of bericht invoegen.",
|
||||
"link-copied": "Link werd naar het klembord gekopieerd. Je kunt deze nu in een email of bericht invoegen.",
|
||||
"link-deleted": "De link werd op {date} verwijderd.",
|
||||
"link-expired": "De link is niet meer geldig. De geldigheid is op {date} afgelopen.",
|
||||
"link-hint": "Attentie: Iedereen kan deze link inwisselen. Geef het alsjeblieft niet door!",
|
||||
"link-overview": "Overzicht links",
|
||||
"links_count": "Actieve links",
|
||||
"links_sum": "Open links en QR-Codes",
|
||||
"no-account": "Je hebt nog geen Gradido rekening?",
|
||||
"no-redeem": "Je mag je eigen link niet inwisselen!",
|
||||
"not-copied": "Jouw apparaat laat het kopiëren helaas niet toe! Kopieer de link alsjeblieft met de hand!",
|
||||
"redeem": "Inwisselen",
|
||||
"redeem-text": "Wil je het bedrag nu inwisselen?",
|
||||
"redeemed": "Succesvol ingewisseld! Op jouw rekening werden {n} GDD bijgeschreven.",
|
||||
"redeemed-at": "De link werd al op {date} ingewisseld.",
|
||||
"redeemed-title": "ingewisseld",
|
||||
"to-login": "Inloggen",
|
||||
"to-register": "Registreer een nieuwe rekening.",
|
||||
"validUntil": "Geldig tot",
|
||||
"validUntilDate": "De link is geldig tot {date}."
|
||||
},
|
||||
"gdt": {
|
||||
"calculation": "Berekening van de Gradido Transform",
|
||||
"contribution": "Bedrag",
|
||||
"conversion": "Omrekening",
|
||||
"conversion-gdt-euro": "Omrekening Euro / Gradido Transform (GDT)",
|
||||
"credit": "Krediet",
|
||||
"factor": "Factor",
|
||||
"formula": "Berekeningsformule",
|
||||
"funding": "Naar de donaties",
|
||||
"gdt": "Gradido Transform",
|
||||
"gdt-received": "Gradido Transform (GDT) ontvangen",
|
||||
"no-transactions": "Je hebt nog geen Gradido Transform (GDT).",
|
||||
"not-reachable": "De GDT server is niet bereikbaar.",
|
||||
"publisher": "Jouw geworven lid heeft een bijdrage betaald ",
|
||||
"raise": "Verhoging",
|
||||
"recruited-member": "Uitgenodigd lid"
|
||||
},
|
||||
"language": "Taal",
|
||||
"link-load": "de laatste link herladen | de laatste links herladen | verdere {n} links herladen",
|
||||
"login": "Aanmelding",
|
||||
"math": {
|
||||
"aprox": "~",
|
||||
"asterisk": "*",
|
||||
"equal": "=",
|
||||
"minus": "−",
|
||||
"pipe": "|"
|
||||
},
|
||||
"message": {
|
||||
"activateEmail": "Jouw rekening werd nog niet geactiveerd. Controleer aljeblieft jouw email en klik de activeringslink. Of vraag een nieuwe activeringlink via de Wachtwoord-Reset-Pagina aan.",
|
||||
"checkEmail": "Jouw email werd succesvol geverifieerd. Je kunt je nu aanmelden.",
|
||||
"email": "We hebben jou een email gestuurd.",
|
||||
"errorTitle": "Opgelet!",
|
||||
"register": "Je bent nu geregistreerd. Controleer alsjeblieft je emails en klik op de activeringslink.",
|
||||
"reset": "Jouw wachtwoord werd gewijzigd.",
|
||||
"title": "Dankjewel!",
|
||||
"unsetPassword": "Jouw wachtwoord werd nog niet ingesteld. Doe het alsjeblieft opnieuw."
|
||||
},
|
||||
"navigation": {
|
||||
"admin_area": "Beheerder",
|
||||
"community": "Gemeenschap",
|
||||
"info": "Informatie",
|
||||
"logout": "Afmelden",
|
||||
"members_area": "Ledenbestand",
|
||||
"overview": "Overzicht",
|
||||
"profile": "Mijn profiel",
|
||||
"send": "Sturen",
|
||||
"support": "Support",
|
||||
"transactions": "Transacties"
|
||||
},
|
||||
"qrCode": "QR Code",
|
||||
"send_gdd": "GDD sturen",
|
||||
"send_per_link": "GDD per link versturen",
|
||||
"session": {
|
||||
"extend": "Aangemeld blijven",
|
||||
"lightText": "Wanneer je langer dan 10 minuten geen actie ondernomen hebt, word je om veiligheidsredenen afgemeld.",
|
||||
"logoutIn": "Afmelden in ",
|
||||
"warningText": "Ben je er nog?"
|
||||
},
|
||||
"settings": {
|
||||
"language": {
|
||||
"changeLanguage": "Taal veranderen",
|
||||
"de": "Duits",
|
||||
"en": "Engels",
|
||||
"es": "Spaans",
|
||||
"fr": "Frans",
|
||||
"nl": "Nederlands",
|
||||
"success": "Jouw taal werd succesvol veranderd."
|
||||
},
|
||||
"name": {
|
||||
"change-name": "Naam veranderen",
|
||||
"change-success": "Jouw naam werd succesvol veranderd."
|
||||
},
|
||||
"newsletter": {
|
||||
"newsletter": "Informatie per email",
|
||||
"newsletterFalse": "Je ontvangt geen informatie per email.",
|
||||
"newsletterTrue": "Je ontvangt informatie per email."
|
||||
},
|
||||
"password": {
|
||||
"change-password": "Wachtwoord veranderen",
|
||||
"forgot_pwd": "Wachtwoord vergeten?",
|
||||
"resend_subtitle": "Jouw activeringslink is afgelopen. Je kunt hier een nieuwe aanvragen.",
|
||||
"reset": "Wachtwoord opnieuw instellen",
|
||||
"reset-password": {
|
||||
"text": "Stel een nieuw wachtwoord in, waarmee je je voortaan in jouw Gradido-rekening wilt aanmelden."
|
||||
},
|
||||
"send_now": "Nu versturen",
|
||||
"set": "Wachtwoord instellen",
|
||||
"set-password": {
|
||||
"text": "Sla nu je nieuwe wachtwoord, waarmee je je voortaan in jouw Gradido-rekening kunt aanmelden, op."
|
||||
},
|
||||
"subtitle": "Wanneer je het wachtwoord hebt vergeten, kun je het hier opnieuw instellen."
|
||||
}
|
||||
},
|
||||
"signin": "Aanmelden",
|
||||
"signup": "Registreren",
|
||||
"site": {
|
||||
"forgotPassword": {
|
||||
"heading": "Geef alsjeblieft jouw email, waarmee je bij Gradido aangemeld bent."
|
||||
},
|
||||
"login": {
|
||||
"heading": "Meld je met jouw inloggegevens aan. Sla deze altijd veilig op!"
|
||||
},
|
||||
"resetPassword": {
|
||||
"heading": "Vul alsjeblieft jouw wachtwoord in, en herhaal het."
|
||||
},
|
||||
"signup": {
|
||||
"agree": "Ik ga akkoord met <a href='https://gradido.net/de/datenschutz/' target='_blank' >Datenschutzerklärung</a>.",
|
||||
"dont_match": "De wachtwoorden zijn niet gelijk.",
|
||||
"heading": "Schrijf je in door alle gegevens volledig en in de juiste velden in te vullen.",
|
||||
"lowercase": "Een kleine letter is noodzakelijk.",
|
||||
"minimum": "Minstens 8 tekens.",
|
||||
"no-whitespace": "Geen spaties en tabs",
|
||||
"one_number": "Getal noodzakelijk.",
|
||||
"special-char": "Speciaal teken noodzakelijk (bijv. _ of é)",
|
||||
"uppercase": "Hoofdletter noodzakelijk."
|
||||
}
|
||||
},
|
||||
"statistic": {
|
||||
"activeUsers": "Actieve leden",
|
||||
"deletedUsers": "Verwijderde leden",
|
||||
"totalGradidoAvailable": "Totaal GDD in omloop",
|
||||
"totalGradidoCreated": "Totaal GDD geschept",
|
||||
"totalGradidoDecayed": "Totaal GDD vervallen",
|
||||
"totalGradidoUnbookedDecayed": "Totaal niet geboekte GDD vervallen"
|
||||
},
|
||||
"success": "Succes",
|
||||
"time": {
|
||||
"days": "Dagen",
|
||||
"hours": "Uren",
|
||||
"minutes": "Minuten",
|
||||
"month": "Maand",
|
||||
"months": "Maanden",
|
||||
"seconds": "Seconden",
|
||||
"years": "Jaar"
|
||||
},
|
||||
"transaction": {
|
||||
"gdd-text": "Gradido transacties",
|
||||
"gdt-text": "GradidoTransform transacties",
|
||||
"nullTransactions": "Je hebt nog geen transacties op jouw rekening.",
|
||||
"receiverDeleted": "De rekening van de ontvanger werd verwijderd",
|
||||
"receiverNotFound": "Ontvanger niet gevonden",
|
||||
"show_all": "Alle <strong>{count}</strong> Transacties bekijken."
|
||||
},
|
||||
"transaction-link": {
|
||||
"send_you": "stuurt jou"
|
||||
},
|
||||
"via_link": "via een link",
|
||||
"welcome": "Welkom in de gemeenschap"
|
||||
}
|
||||
@ -30,7 +30,8 @@ ${this.$store.state.firstName} ${this.$t('transaction-link.send_you')} ${this.am
|
||||
"${this.memo}"
|
||||
${this.$t('gdd_per_link.credit-your-gradido')} ${this.$t('gdd_per_link.validUntilDate', {
|
||||
date: this.$d(new Date(this.validUntil), 'short'),
|
||||
})}`,
|
||||
})}
|
||||
${this.$t('gdd_per_link.link-hint')}`,
|
||||
)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-and-text-copied'))
|
||||
|
||||
130
frontend/src/pages/InfoStatistic.spec.js
Normal file
@ -0,0 +1,130 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import InfoStatistic from './InfoStatistic'
|
||||
import { toastErrorSpy } from '../../test/testSetup'
|
||||
import { listContributionLinks, communityStatistics, searchAdminUsers } from '@/graphql/queries'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
listContributionLinks: {
|
||||
count: 2,
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
amount: 200,
|
||||
name: 'Dokumenta 2017',
|
||||
memo: 'Vielen Dank für deinen Besuch bei der Dokumenta 2017',
|
||||
cycle: 'ONCE',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
amount: 200,
|
||||
name: 'Dokumenta 2022',
|
||||
memo: 'Vielen Dank für deinen Besuch bei der Dokumenta 2022',
|
||||
cycle: 'ONCE',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
searchAdminUsers: {
|
||||
userCount: 2,
|
||||
userList: [
|
||||
{ firstName: 'Peter', lastName: 'Lustig' },
|
||||
{ firstName: 'Super', lastName: 'Admin' },
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
communityStatistics: {
|
||||
totalUsers: 3113,
|
||||
activeUsers: 1057,
|
||||
deletedUsers: 35,
|
||||
totalGradidoCreated: '4083774.05000000000000000000',
|
||||
totalGradidoDecayed: '-1062639.13634129622923372197',
|
||||
totalGradidoAvailable: '2513565.869444365732411569',
|
||||
totalGradidoUnbookedDecayed: '-500474.6738366222166261272',
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockResolvedValue('default')
|
||||
|
||||
describe('InfoStatistic', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$t: jest.fn((t) => t),
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(InfoStatistic, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the info page', () => {
|
||||
expect(wrapper.find('div.info-statistic').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('calls listContributionLinks', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
query: listContributionLinks,
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('calls searchAdminUsers', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
query: searchAdminUsers,
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('calls getCommunityStatistics', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
query: communityStatistics,
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('error apolloQueryMock', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
apolloQueryMock.mockRejectedValue({
|
||||
message: 'uups',
|
||||
})
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('toasts three error messages', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith(
|
||||
'listContributionLinks has no result, use default data',
|
||||
)
|
||||
expect(toastErrorSpy).toBeCalledWith('searchAdminUsers has no result, use default data')
|
||||
expect(toastErrorSpy).toBeCalledWith('communityStatistics has no result, use default data')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
166
frontend/src/pages/InfoStatistic.vue
Normal file
@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="info-statistic">
|
||||
<b-container>
|
||||
<div class="h3">{{ $t('community.community') }}</div>
|
||||
<div class="h1">{{ CONFIG.COMMUNITY_NAME }}</div>
|
||||
<div>
|
||||
{{ CONFIG.COMMUNITY_DESCRIPTION }}
|
||||
</div>
|
||||
<div>
|
||||
{{ CONFIG.COMMUNITY_URL }}
|
||||
</div>
|
||||
</b-container>
|
||||
|
||||
<hr />
|
||||
<b-container>
|
||||
<div class="h3">{{ $t('community.openContributionLinks') }}</div>
|
||||
<small>
|
||||
{{
|
||||
$t('community.openContributionLinkText', {
|
||||
name: CONFIG.COMMUNITY_NAME,
|
||||
count,
|
||||
})
|
||||
}}
|
||||
</small>
|
||||
<ul>
|
||||
<li v-for="item in itemsContributionLinks" v-bind:key="item.id">
|
||||
<div>{{ item.name }}</div>
|
||||
<div>{{ item.memo }}</div>
|
||||
<div>
|
||||
{{ item.amount | GDD }}
|
||||
<span v-if="item.cycle === 'ONCE'">{{ $t('contribution-link.unique') }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</b-container>
|
||||
<hr />
|
||||
<b-container>
|
||||
<div class="h3">{{ $t('community.moderators') }}</div>
|
||||
<ul>
|
||||
<li v-for="item in itemsAdminUser" v-bind:key="item.id">
|
||||
{{ item.firstName }} {{ item.lastName }}
|
||||
</li>
|
||||
</ul>
|
||||
<b-link href="mailto: abc@example.com">{{ supportMail }}</b-link>
|
||||
</b-container>
|
||||
<hr />
|
||||
<b-container>
|
||||
<div class="h3">{{ $t('community.statistic') }}</div>
|
||||
<div>
|
||||
<div>
|
||||
{{ $t('community.members') }}
|
||||
<span class="h4">{{ totalUsers }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.activeUsers') }}
|
||||
<span class="h4">{{ activeUsers }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.deletedUsers') }}
|
||||
<span class="h4">{{ deletedUsers }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.totalGradidoCreated') }}
|
||||
<span class="h4">{{ totalGradidoCreated | GDD }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.totalGradidoDecayed') }}
|
||||
<span class="h4">{{ totalGradidoDecayed | GDD }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.totalGradidoAvailable') }}
|
||||
<span class="h4">{{ totalGradidoAvailable | GDD }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('statistic.totalGradidoUnbookedDecayed') }}
|
||||
<span class="h4">{{ totalGradidoUnbookedDecayed | GDD }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</b-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import CONFIG from '@/config'
|
||||
import { listContributionLinks, communityStatistics, searchAdminUsers } from '@/graphql/queries'
|
||||
|
||||
export default {
|
||||
name: 'InfoStatistic',
|
||||
data() {
|
||||
return {
|
||||
CONFIG,
|
||||
count: null,
|
||||
countAdminUser: null,
|
||||
itemsContributionLinks: [],
|
||||
itemsAdminUser: [],
|
||||
supportMail: 'support@supportemail.de',
|
||||
membersCount: '1203',
|
||||
totalUsers: null,
|
||||
activeUsers: null,
|
||||
deletedUsers: null,
|
||||
totalGradidoCreated: null,
|
||||
totalGradidoDecayed: null,
|
||||
totalGradidoAvailable: null,
|
||||
totalGradidoUnbookedDecayed: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getContributionLinks() {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: listContributionLinks,
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
.then((result) => {
|
||||
this.count = result.data.listContributionLinks.count
|
||||
this.itemsContributionLinks = result.data.listContributionLinks.links
|
||||
})
|
||||
.catch(() => {
|
||||
this.toastError('listContributionLinks has no result, use default data')
|
||||
})
|
||||
},
|
||||
getAdminUsers() {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: searchAdminUsers,
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
.then((result) => {
|
||||
this.countAdminUser = result.data.searchAdminUsers.userCount
|
||||
this.itemsAdminUser = result.data.searchAdminUsers.userList
|
||||
})
|
||||
.catch(() => {
|
||||
this.toastError('searchAdminUsers has no result, use default data')
|
||||
})
|
||||
},
|
||||
getCommunityStatistics() {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: communityStatistics,
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
.then((result) => {
|
||||
this.totalUsers = result.data.communityStatistics.totalUsers
|
||||
this.activeUsers = result.data.communityStatistics.activeUsers
|
||||
this.deletedUsers = result.data.communityStatistics.deletedUsers
|
||||
this.totalGradidoCreated = result.data.communityStatistics.totalGradidoCreated
|
||||
this.totalGradidoDecayed = result.data.communityStatistics.totalGradidoDecayed
|
||||
this.totalGradidoAvailable = result.data.communityStatistics.totalGradidoAvailable
|
||||
this.totalGradidoUnbookedDecayed =
|
||||
result.data.communityStatistics.totalGradidoUnbookedDecayed
|
||||
})
|
||||
.catch(() => {
|
||||
this.toastError('communityStatistics has no result, use default data')
|
||||
})
|
||||
},
|
||||
updateTransactions(pagination) {
|
||||
this.$emit('update-transactions', pagination)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getContributionLinks()
|
||||
this.getAdminUsers()
|
||||
this.getCommunityStatistics()
|
||||
this.updateTransactions(0)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -283,7 +283,8 @@ describe('Send', () => {
|
||||
'http://localhost/redeem/0123456789\n' +
|
||||
'Testy transaction-link.send_you 56.78 Gradido.\n' +
|
||||
'"Make the best of the link!"\n' +
|
||||
'gdd_per_link.credit-your-gradido gdd_per_link.validUntilDate',
|
||||
'gdd_per_link.credit-your-gradido gdd_per_link.validUntilDate\n' +
|
||||
'gdd_per_link.link-hint',
|
||||
)
|
||||
})
|
||||
it('toasts success message', () => {
|
||||
|
||||
@ -50,7 +50,7 @@ describe('router', () => {
|
||||
})
|
||||
|
||||
it('has sixteen routes defined', () => {
|
||||
expect(routes).toHaveLength(18)
|
||||
expect(routes).toHaveLength(19)
|
||||
})
|
||||
|
||||
describe('overview', () => {
|
||||
@ -108,6 +108,28 @@ describe('router', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('community', () => {
|
||||
it('requires authorization', () => {
|
||||
expect(routes.find((r) => r.path === '/community').meta.requiresAuth).toBeTruthy()
|
||||
})
|
||||
|
||||
it('loads the "Community" page', async () => {
|
||||
const component = await routes.find((r) => r.path === '/community').component()
|
||||
expect(component.default.name).toBe('Community')
|
||||
})
|
||||
})
|
||||
|
||||
describe('info', () => {
|
||||
it('requires authorization', () => {
|
||||
expect(routes.find((r) => r.path === '/information').meta.requiresAuth).toBeTruthy()
|
||||
})
|
||||
|
||||
it('loads the "InfoStatistic" page', async () => {
|
||||
const component = await routes.find((r) => r.path === '/information').component()
|
||||
expect(component.default.name).toBe('InfoStatistic')
|
||||
})
|
||||
})
|
||||
|
||||
describe('gdt', () => {
|
||||
it('requires authorization', () => {
|
||||
expect(routes.find((r) => r.path === '/gdt').meta.requiresAuth).toBeTruthy()
|
||||
|
||||
@ -54,6 +54,13 @@ const routes = [
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/information',
|
||||
component: () => import('@/pages/InfoStatistic.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/login/:code?',
|
||||
component: () => import('@/pages/Login.vue'),
|
||||
|
||||