mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge remote-tracking branch 'origin/master' into 2072-feature-usecase-contribution-messaging
This commit is contained in:
commit
0aafa02498
47
CHANGELOG.md
47
CHANGELOG.md
@ -4,8 +4,55 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [1.11.0](https://github.com/gradido/gradido/compare/1.10.1...1.11.0)
|
||||
|
||||
- Fix navbar community item [`#2102`](https://github.com/gradido/gradido/pull/2102)
|
||||
- Add validation date info to copied text after transaction link creation [`#2101`](https://github.com/gradido/gradido/pull/2101)
|
||||
- Remove member area from menu (desktop and mobile), when user has no elopage account [`#2099`](https://github.com/gradido/gradido/pull/2099)
|
||||
- fix: Use Inner Join for Contribution and User [`#2100`](https://github.com/gradido/gradido/pull/2100)
|
||||
- Enable copying the link, username, amount, and memo text after transaction link creation [`#2098`](https://github.com/gradido/gradido/pull/2098)
|
||||
- feat: Insert Missing Contributions Migration [`#2053`](https://github.com/gradido/gradido/pull/2053)
|
||||
- [Bug] Wallet improvments for Contributions [`#2090`](https://github.com/gradido/gradido/pull/2090)
|
||||
- [Fix] Add createdAt & contributionDate to ContributionListItems [`#2093`](https://github.com/gradido/gradido/pull/2093)
|
||||
- fix: 🍰 Reset Amount In Contribution Form And Write A Test [`#2086`](https://github.com/gradido/gradido/pull/2086)
|
||||
- [Feat] Replace logic to validation-provider. [`#2088`](https://github.com/gradido/gradido/pull/2088)
|
||||
- fix: Add Confirm Dialog on Delete Contribution [`#2087`](https://github.com/gradido/gradido/pull/2087)
|
||||
- fix: Admin Cannot Edit User Contribution [`#2085`](https://github.com/gradido/gradido/pull/2085)
|
||||
- fix: Update contribution_date when Moved by Seed [`#2083`](https://github.com/gradido/gradido/pull/2083)
|
||||
- chore: 🍰 Provide Volume For Backend Log-Files In Docker [`#2067`](https://github.com/gradido/gradido/pull/2067)
|
||||
- feat: 🍰 Community Contribution Site And Form [`#2042`](https://github.com/gradido/gradido/pull/2042)
|
||||
- [Refactor] Move MEMO_MIN_CHARS and MEMO_MAX_CHARS to const file [`#2082`](https://github.com/gradido/gradido/pull/2082)
|
||||
- Fix: Test memo length on createContribution & updateContribution [`#2080`](https://github.com/gradido/gradido/pull/2080)
|
||||
- chore: 🍰 Change `image` Entries In Docker Compose Files And Get Apple M1 Running [`#2050`](https://github.com/gradido/gradido/pull/2050)
|
||||
- fix: Windows 0D 0A Linebreaks to Unix 0A [`#2064`](https://github.com/gradido/gradido/pull/2064)
|
||||
- Add contributionDate to the Contribution object. [`#2066`](https://github.com/gradido/gradido/pull/2066)
|
||||
- fix: Add Contributions to User [`#2062`](https://github.com/gradido/gradido/pull/2062)
|
||||
- Feat: ContributionResolver - delete mutation [`#2035`](https://github.com/gradido/gradido/pull/2035)
|
||||
- Fix: Add count to list contributions [`#2061`](https://github.com/gradido/gradido/pull/2061)
|
||||
- docu: Explain how `.env` Files are Working [`#2022`](https://github.com/gradido/gradido/pull/2022)
|
||||
- [WIP] 1794 feature event protocol 1 implement the basics of the business event protocol [`#1997`](https://github.com/gradido/gradido/pull/1997)
|
||||
- docs: 🍰 Document The Setup Of The GraphQL Playground [`#2060`](https://github.com/gradido/gradido/pull/2060)
|
||||
- Feat: List all contribution [`#2057`](https://github.com/gradido/gradido/pull/2057)
|
||||
- feat: Do not log IntrospectionQuery from Query Browser [`#2059`](https://github.com/gradido/gradido/pull/2059)
|
||||
- Feat: Add confirmedBy and confirmedAt for the contribution query. [`#2052`](https://github.com/gradido/gradido/pull/2052)
|
||||
- Add open creations to webapp [`#2048`](https://github.com/gradido/gradido/pull/2048)
|
||||
- Prevent session expiration modal from displaying negative seconds, when session is expired for more than 0 seconds [`#2054`](https://github.com/gradido/gradido/pull/2054)
|
||||
- feat: mutation contribution update [`#2032`](https://github.com/gradido/gradido/pull/2032)
|
||||
- feat: Login Returns Open Creations for User [`#2046`](https://github.com/gradido/gradido/pull/2046)
|
||||
- Migrate transaction to valid dataset for gradido node [`#2029`](https://github.com/gradido/gradido/pull/2029)
|
||||
- feat: implement contribution list query [`#2031`](https://github.com/gradido/gradido/pull/2031)
|
||||
- add code for moving user creation date if transaction before exist [`#2034`](https://github.com/gradido/gradido/pull/2034)
|
||||
- change text from page [`#2037`](https://github.com/gradido/gradido/pull/2037)
|
||||
- Transaction link: copy link, text and more [`#2030`](https://github.com/gradido/gradido/pull/2030)
|
||||
- change welcome in community text [`#2025`](https://github.com/gradido/gradido/pull/2025)
|
||||
- changed link color in navbar and language switch [`#2024`](https://github.com/gradido/gradido/pull/2024)
|
||||
- feat: ContributionResolver - createContribution [`#2009`](https://github.com/gradido/gradido/pull/2009)
|
||||
|
||||
#### [1.10.1](https://github.com/gradido/gradido/compare/1.10.0...1.10.1)
|
||||
|
||||
> 30 June 2022
|
||||
|
||||
- release: 1.10.1 [`#2021`](https://github.com/gradido/gradido/pull/2021)
|
||||
- automatic session logout with info modal [`#2001`](https://github.com/gradido/gradido/pull/2001)
|
||||
- 1910 separate text for the slideshow images. [`#1998`](https://github.com/gradido/gradido/pull/1998)
|
||||
- Origin/1921 additional parameter checks for createContributionLinks [`#1996`](https://github.com/gradido/gradido/pull/1996)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
|
||||
@ -28,7 +28,7 @@ const propsData = {
|
||||
amount: 210,
|
||||
memo: 'Aktives Grundeinkommen für Januar 2022',
|
||||
date: '2022-01-01T00:00:00.000Z',
|
||||
moderator: 1,
|
||||
moderator: null,
|
||||
creation: [790, 1000, 1000],
|
||||
__typename: 'PendingCreation',
|
||||
},
|
||||
@ -66,7 +66,7 @@ const propsData = {
|
||||
},
|
||||
},
|
||||
{ key: 'moderator', label: 'moderator' },
|
||||
{ key: 'edit_creation', label: 'edit' },
|
||||
{ key: 'editCreation', label: 'edit' },
|
||||
{ key: 'confirm', label: 'save' },
|
||||
],
|
||||
toggleDetails: false,
|
||||
@ -113,6 +113,10 @@ describe('OpenCreationsTable', () => {
|
||||
expect(wrapper.findAll('tr').at(1).find('.bi-pencil-square').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has no button.bi-pencil-square for user contribution ', () => {
|
||||
expect(wrapper.findAll('tr').at(2).find('.bi-pencil-square').exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('show edit details', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findAll('tr').at(1).find('.bi-pencil-square').trigger('click')
|
||||
|
||||
@ -11,8 +11,14 @@
|
||||
<b-icon icon="x" variant="light"></b-icon>
|
||||
</b-button>
|
||||
</template>
|
||||
<template #cell(edit_creation)="row">
|
||||
<b-button variant="info" size="md" @click="rowToggleDetails(row, 0)" class="mr-2">
|
||||
<template #cell(editCreation)="row">
|
||||
<b-button
|
||||
v-if="row.item.moderator"
|
||||
variant="info"
|
||||
size="md"
|
||||
@click="rowToggleDetails(row, 0)"
|
||||
class="mr-2"
|
||||
>
|
||||
<b-icon :icon="row.detailsShowing ? 'x' : 'pencil-square'" aria-label="Help"></b-icon>
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
:per-page="perPage"
|
||||
:total-rows="rows"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
"creation_form": {
|
||||
"creation_failed": "Ausstehende Schöpfung für {email} konnte nicht erzeugt werden.",
|
||||
"creation_for": "Aktives Grundeinkommen für",
|
||||
"deleteNow": "Möchtest du diesen Beitrag zur Gemeinschaft wirklich löschen?",
|
||||
"enter_text": "Text eintragen",
|
||||
"form": "Schöpfungsformular",
|
||||
"min_characters": "Mindestens 10 Zeichen eingeben",
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
"creation_form": {
|
||||
"creation_failed": "Could not create pending creation for {email}",
|
||||
"creation_for": "Active Basic Income for",
|
||||
"deleteNow": "Do you really want to delete this contribution to the community?",
|
||||
"enter_text": "Enter text",
|
||||
"form": "Creation form",
|
||||
"min_characters": "Enter at least 10 characters",
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
per-page="perPage"
|
||||
:total-rows="rows"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
</b-col>
|
||||
<b-col cols="12" lg="6" class="shadow p-3 mb-5 rounded bg-info">
|
||||
|
||||
@ -18,7 +18,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
amount: 500,
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderator: 0,
|
||||
moderator: 2,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -28,7 +28,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
amount: 1000000,
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderator: 0,
|
||||
moderator: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -80,28 +80,54 @@ describe('CreationConfirm', () => {
|
||||
})
|
||||
|
||||
describe('remove creation with success', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findAll('tr').at(1).findAll('button').at(0).trigger('click')
|
||||
})
|
||||
let spy
|
||||
|
||||
it('calls the adminDeleteContribution mutation', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith({
|
||||
mutation: adminDeleteContribution,
|
||||
variables: { id: 1 },
|
||||
describe('admin confirms deletion', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve('some value'))
|
||||
await wrapper.findAll('tr').at(1).findAll('button').at(0).trigger('click')
|
||||
})
|
||||
|
||||
it('opens a modal', () => {
|
||||
expect(spy).toBeCalled()
|
||||
})
|
||||
|
||||
it('calls the adminDeleteContribution mutation', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith({
|
||||
mutation: adminDeleteContribution,
|
||||
variables: { id: 1 },
|
||||
})
|
||||
})
|
||||
|
||||
it('commits openCreationsMinus to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('creation_form.toasted_delete')
|
||||
})
|
||||
})
|
||||
|
||||
it('commits openCreationsMinus to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
|
||||
})
|
||||
describe('admin cancels deletion', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(false))
|
||||
await wrapper.findAll('tr').at(1).findAll('button').at(0).trigger('click')
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('creation_form.toasted_delete')
|
||||
it('does not call the adminDeleteContribution mutation', () => {
|
||||
expect(apolloMutateMock).not.toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('remove creation with error', () => {
|
||||
let spy
|
||||
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve('some value'))
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' })
|
||||
await wrapper.findAll('tr').at(1).findAll('button').at(0).trigger('click')
|
||||
})
|
||||
|
||||
@ -34,20 +34,23 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
removeCreation(item) {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: adminDeleteContribution,
|
||||
variables: {
|
||||
id: item.id,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.updatePendingCreations(item.id)
|
||||
this.toastSuccess(this.$t('creation_form.toasted_delete'))
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
this.$bvModal.msgBoxConfirm(this.$t('creation_form.deleteNow')).then(async (value) => {
|
||||
if (value)
|
||||
await this.$apollo
|
||||
.mutate({
|
||||
mutation: adminDeleteContribution,
|
||||
variables: {
|
||||
id: item.id,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.updatePendingCreations(item.id)
|
||||
this.toastSuccess(this.$t('creation_form.toasted_delete'))
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
})
|
||||
},
|
||||
confirmCreation() {
|
||||
this.$apollo
|
||||
@ -114,7 +117,7 @@ export default {
|
||||
},
|
||||
},
|
||||
{ key: 'moderator', label: this.$t('moderator') },
|
||||
{ key: 'edit_creation', label: this.$t('edit') },
|
||||
{ key: 'editCreation', label: this.$t('edit') },
|
||||
{ key: 'confirm', label: this.$t('save') },
|
||||
]
|
||||
},
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
per-page="perPage"
|
||||
:total-rows="rows"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
|
||||
@ -10,7 +10,7 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0043-add_event_protocol_table',
|
||||
DB_VERSION: '0044-insert_missing_contributions',
|
||||
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
|
||||
|
||||
@ -3,7 +3,7 @@ import { Context, getUser } from '@/server/context'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { Contribution as dbContribution } from '@entity/Contribution'
|
||||
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
|
||||
import { FindOperator, IsNull } from '@dbTools/typeorm'
|
||||
import { FindOperator, IsNull, getConnection } from '@dbTools/typeorm'
|
||||
import ContributionArgs from '@arg/ContributionArgs'
|
||||
import Paginated from '@arg/Paginated'
|
||||
import { Order } from '@enum/Order'
|
||||
@ -106,14 +106,15 @@ export class ContributionResolver {
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
): Promise<ContributionListResult> {
|
||||
const [dbContributions, count] = await dbContribution.findAndCount({
|
||||
relations: ['user'],
|
||||
order: {
|
||||
createdAt: order,
|
||||
},
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
const [dbContributions, count] = await getConnection()
|
||||
.createQueryBuilder()
|
||||
.select('c')
|
||||
.from(dbContribution, 'c')
|
||||
.innerJoinAndSelect('c.user', 'u')
|
||||
.orderBy('c.createdAt', order)
|
||||
.limit(pageSize)
|
||||
.offset((currentPage - 1) * pageSize)
|
||||
.getManyAndCount()
|
||||
return new ContributionListResult(
|
||||
count,
|
||||
dbContributions.map(
|
||||
|
||||
@ -35,12 +35,17 @@ export const creationFactory = async (
|
||||
if (creation.confirmed) {
|
||||
await mutate({ mutation: confirmContribution, variables: { id: pendingCreation.id } })
|
||||
|
||||
const confirmedCreation = await Contribution.findOneOrFail({ id: pendingCreation.id })
|
||||
|
||||
if (creation.moveCreationDate) {
|
||||
const transaction = await Transaction.findOneOrFail({
|
||||
where: { userId: user.id, creationDate: new Date(creation.creationDate) },
|
||||
order: { balanceDate: 'DESC' },
|
||||
})
|
||||
if (transaction.decay.equals(0) && transaction.creationDate) {
|
||||
confirmedCreation.contributionDate = new Date(
|
||||
nMonthsBefore(transaction.creationDate, creation.moveCreationDate),
|
||||
)
|
||||
transaction.creationDate = new Date(
|
||||
nMonthsBefore(transaction.creationDate, creation.moveCreationDate),
|
||||
)
|
||||
@ -48,6 +53,7 @@ export const creationFactory = async (
|
||||
nMonthsBefore(transaction.balanceDate, creation.moveCreationDate),
|
||||
)
|
||||
await transaction.save()
|
||||
await confirmedCreation.save()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
34
database/migrations/0044-insert_missing_contributions.ts
Normal file
34
database/migrations/0044-insert_missing_contributions.ts
Normal file
@ -0,0 +1,34 @@
|
||||
/* MIGRATION TO INSERT contributions for all transactions with type creation that do not have a contribution yet */
|
||||
|
||||
/* 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(
|
||||
`INSERT INTO gradido_community.contributions
|
||||
(user_id, created_at, contribution_date, memo, amount, moderator_id, confirmed_by, confirmed_at, transaction_id)
|
||||
SELECT
|
||||
user_id,
|
||||
balance_date,
|
||||
creation_date AS contribution_date,
|
||||
memo,
|
||||
amount,
|
||||
20 AS moderator_id,
|
||||
502 AS confirmed_by,
|
||||
balance_date AS confirmed_at,
|
||||
id
|
||||
FROM
|
||||
gradido_community.transactions
|
||||
WHERE
|
||||
type_id = 1
|
||||
AND NOT EXISTS(
|
||||
SELECT * FROM gradido_community.contributions
|
||||
WHERE gradido_community.contributions.transaction_id = gradido_community.transactions.id);`,
|
||||
)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(
|
||||
'DELETE FROM `contributions` WHERE `contributions`.`moderator_id` = 20 AND `contributions`.`confirmed_by` = 502 AND `contributions`.`created_at` = `contributions`.`confirmed_at`;',
|
||||
)
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-database",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/database",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bootstrap-vue-gradido-wallet",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node run/server.js",
|
||||
|
||||
@ -3,8 +3,11 @@
|
||||
<b-input-group v-if="canCopyLink" size="lg" class="mb-3" prepend="Link">
|
||||
<b-form-input :value="link" type="text" readonly></b-form-input>
|
||||
<b-input-group-append>
|
||||
<b-button size="sm" text="Button" variant="primary" @click="CopyLink">
|
||||
{{ $t('gdd_per_link.copy') }}
|
||||
<b-button size="sm" text="Button" variant="primary" @click="copyLinkWithText">
|
||||
{{ $t('gdd_per_link.copy-link-with-text') }}
|
||||
</b-button>
|
||||
<b-button size="sm" text="Button" variant="primary" @click="copyLink">
|
||||
{{ $t('gdd_per_link.copy-link') }}
|
||||
</b-button>
|
||||
<b-button variant="primary" class="text-light" @click="$emit('show-qr-code-button')">
|
||||
<b-img src="img/svg/qr-code.svg" width="19" class="svg"></b-img>
|
||||
@ -18,29 +21,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { copyLinks } from '../mixins/copyLinks'
|
||||
export default {
|
||||
name: 'ClipboardCopy',
|
||||
props: {
|
||||
link: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
canCopyLink: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
CopyLink() {
|
||||
navigator.clipboard
|
||||
.writeText(this.link)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-copied'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.canCopyLink = false
|
||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||
})
|
||||
},
|
||||
},
|
||||
mixins: [copyLinks],
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
@ -97,24 +97,14 @@ describe('ContentFooter', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('has a link to the members area', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(2).text()).toEqual('navigation.members_area')
|
||||
})
|
||||
|
||||
it('links to the elopage', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(2).attributes('href')).toEqual(
|
||||
'https://elopage.com/s/gradido/sign_in?locale=en',
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the whitepaper', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(3).attributes('href')).toEqual(
|
||||
expect(wrapper.findAll('a.nav-link').at(2).attributes('href')).toEqual(
|
||||
'https://docs.google.com/document/d/1kcX1guOi6tDgnFHD9tf7fB_MneKTx-0nHJxzdN8ygNs/edit?usp=sharing',
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the support', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(4).attributes('href')).toEqual(
|
||||
expect(wrapper.findAll('a.nav-link').at(3).attributes('href')).toEqual(
|
||||
'https://gradido.net/en/contact/',
|
||||
)
|
||||
})
|
||||
@ -142,20 +132,14 @@ describe('ContentFooter', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the German elopage when locale is de', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(2).attributes('href')).toEqual(
|
||||
'https://elopage.com/s/gradido/sign_in?locale=de',
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the German whitepaper when locale is de', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(3).attributes('href')).toEqual(
|
||||
expect(wrapper.findAll('a.nav-link').at(2).attributes('href')).toEqual(
|
||||
'https://docs.google.com/document/d/1jZp-DiiMPI9ZPNXmjsvOQ1BtnfDFfx8BX7CDmA8KKjY/edit?usp=sharing',
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the German support-page when locale is de', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(4).attributes('href')).toEqual(
|
||||
expect(wrapper.findAll('a.nav-link').at(3).attributes('href')).toEqual(
|
||||
'https://gradido.net/de/contact/',
|
||||
)
|
||||
})
|
||||
|
||||
@ -34,12 +34,6 @@
|
||||
<b-nav-item :href="`https://gradido.net/${$i18n.locale}/datenschutz/`" target="_blank">
|
||||
{{ $t('footer.privacy_policy') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item
|
||||
:href="`https://elopage.com/s/gradido/sign_in?locale=${$i18n.locale}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('navigation.members_area') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item
|
||||
:href="
|
||||
$i18n.locale === 'de'
|
||||
|
||||
@ -23,6 +23,9 @@ describe('ContributionForm', () => {
|
||||
creation: ['1000', '1000', '1000'],
|
||||
},
|
||||
},
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
@ -42,8 +45,70 @@ describe('ContributionForm', () => {
|
||||
expect(wrapper.find('div.contribution-form').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('is submit button disable of true', () => {
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled')
|
||||
describe('empty form data', () => {
|
||||
describe('buttons', () => {
|
||||
it('has reset enabled', () => {
|
||||
expect(wrapper.find('button[type="reset"]').attributes('disabled')).toBeFalsy()
|
||||
})
|
||||
|
||||
it('has submit disabled', () => {
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('set contrubtion', () => {
|
||||
describe('fill in form data', () => {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
id: null,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '200',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('buttons', () => {
|
||||
it('has reset enabled', () => {
|
||||
expect(wrapper.find('button[type="reset"]').attributes('disabled')).toBeFalsy()
|
||||
})
|
||||
|
||||
it('has submit enabled', () => {
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('update contrubtion', () => {
|
||||
describe('fill in form data and "id"', () => {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
id: 2,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '200',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('buttons', () => {
|
||||
it('has reset enabled', () => {
|
||||
expect(wrapper.find('button[type="reset"]').attributes('disabled')).toBeFalsy()
|
||||
})
|
||||
|
||||
it('has submit enabled', () => {
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -13,45 +13,50 @@
|
||||
</div>
|
||||
</div>
|
||||
<b-form ref="form" @submit.prevent="submit" class="border p-3">
|
||||
<label>{{ $t('contribution.selectDate') }}</label>
|
||||
<label>{{ $t('contribution.selectDate') }} {{ $t('math.asterisk') }}</label>
|
||||
<b-form-datepicker
|
||||
id="contribution-date"
|
||||
v-model="form.date"
|
||||
size="lg"
|
||||
:locale="$i18n.locale"
|
||||
:max="maximalDate"
|
||||
:min="minimalDate"
|
||||
class="mb-4"
|
||||
reset-value=""
|
||||
:label-no-date-selected="$t('contribution.noDateSelected')"
|
||||
required
|
||||
></b-form-datepicker>
|
||||
<label class="mt-3">{{ $t('contribution.activity') }}</label>
|
||||
<b-form-textarea
|
||||
id="contribution-memo"
|
||||
v-model="form.memo"
|
||||
rows="3"
|
||||
max-rows="6"
|
||||
required
|
||||
:minlength="minlength"
|
||||
:maxlength="maxlength"
|
||||
></b-form-textarea>
|
||||
<div
|
||||
v-show="form.memo.length > 0"
|
||||
class="text-right"
|
||||
:class="form.memo.length < minlength ? 'text-danger' : 'text-success'"
|
||||
>
|
||||
{{ form.memo.length }}
|
||||
<span v-if="form.memo.length < minlength">{{ $t('math.lower') }} {{ minlength }}</span>
|
||||
<span v-else>{{ $t('math.divide') }} {{ maxlength }}</span>
|
||||
</div>
|
||||
<label class="mt-3">{{ $t('form.amount') }}</label>
|
||||
<b-input-group size="lg" prepend="GDD" append=".00">
|
||||
<template #nav-prev-year><span></span></template>
|
||||
<template #nav-next-year><span></span></template>
|
||||
</b-form-datepicker>
|
||||
<validation-provider
|
||||
:rules="{
|
||||
min: minlength,
|
||||
max: maxlength,
|
||||
}"
|
||||
:name="$t('form.message')"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<label class="mt-3">{{ $t('contribution.activity') }} {{ $t('math.asterisk') }}</label>
|
||||
<b-form-textarea
|
||||
id="contribution-memo"
|
||||
v-model="form.memo"
|
||||
rows="3"
|
||||
max-rows="6"
|
||||
:placeholder="$t('contribution.yourActivity')"
|
||||
required
|
||||
></b-form-textarea>
|
||||
<b-col v-if="errors">
|
||||
<span v-for="error in errors" class="errors" :key="error">{{ error }}</span>
|
||||
</b-col>
|
||||
</validation-provider>
|
||||
<label class="mt-3">{{ $t('form.amount') }} {{ $t('math.asterisk') }}</label>
|
||||
<b-input-group size="lg" prepend="GDD">
|
||||
<b-form-input
|
||||
id="contribution-amount"
|
||||
v-model="form.amount"
|
||||
type="number"
|
||||
min="1"
|
||||
:max="isThisMonth ? maxGddThisMonth : maxGddLastMonth"
|
||||
type="text"
|
||||
:formatter="numberFormat"
|
||||
></b-form-input>
|
||||
</b-input-group>
|
||||
<div
|
||||
@ -68,17 +73,18 @@
|
||||
</div>
|
||||
<b-row class="mt-3">
|
||||
<b-col>
|
||||
<b-button type="button" variant="light" @click.prevent="reset">
|
||||
{{ $t('form.reset') }}
|
||||
<b-button class="test-cancel" type="reset" variant="secondary" @click="reset">
|
||||
{{ $t('form.cancel') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button class="test-submit" type="submit" variant="primary" :disabled="disabled">
|
||||
{{ value.id ? $t('form.edit') : $t('contribution.submit') }}
|
||||
{{ form.id ? $t('form.change') : $t('contribution.submit') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
<p class="p-2">{{ $t('math.asterisk') }} {{ $t('form.mandatoryField') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@ -90,16 +96,19 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
minlength: 50,
|
||||
minlength: 5,
|
||||
maxlength: 255,
|
||||
maximalDate: new Date(),
|
||||
form: this.value,
|
||||
id: this.value.id,
|
||||
form: this.value, // includes 'id'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
numberFormat(value) {
|
||||
return value.replace(/\D/g, '')
|
||||
},
|
||||
submit() {
|
||||
if (this.value.id) {
|
||||
this.form.amount = this.numberFormat(this.form.amount)
|
||||
if (this.form.id) {
|
||||
this.$emit('update-contribution', this.form)
|
||||
} else {
|
||||
this.$emit('set-contribution', this.form)
|
||||
@ -108,9 +117,10 @@ export default {
|
||||
},
|
||||
reset() {
|
||||
this.$refs.form.reset()
|
||||
this.form.id = null
|
||||
this.form.date = ''
|
||||
this.id = null
|
||||
this.form.memo = ''
|
||||
this.form.amount = ''
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
@ -125,8 +135,8 @@ export default {
|
||||
if (
|
||||
this.form.date === '' ||
|
||||
this.form.memo.length < this.minlength ||
|
||||
this.form.amount <= 0 ||
|
||||
this.form.amount > 1000 ||
|
||||
parseInt(this.form.amount) <= 0 ||
|
||||
parseInt(this.form.amount) > 1000 ||
|
||||
(this.isThisMonth && parseInt(this.form.amount) > parseInt(this.maxGddThisMonth)) ||
|
||||
(!this.isThisMonth && parseInt(this.form.amount) > parseInt(this.maxGddLastMonth))
|
||||
)
|
||||
@ -153,16 +163,21 @@ export default {
|
||||
},
|
||||
maxGddLastMonth() {
|
||||
// When edited, the amount is added back on top of the amount
|
||||
return this.value.id && !this.isThisMonth
|
||||
return this.form.id && !this.isThisMonth
|
||||
? parseInt(this.$store.state.creation[1]) + parseInt(this.updateAmount)
|
||||
: this.$store.state.creation[1]
|
||||
},
|
||||
maxGddThisMonth() {
|
||||
// When edited, the amount is added back on top of the amount
|
||||
return this.value.id && this.isThisMonth
|
||||
return this.form.id && this.isThisMonth
|
||||
? parseInt(this.$store.state.creation[2]) + parseInt(this.updateAmount)
|
||||
: this.$store.state.creation[2]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
span.errors {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
:per-page="pageSize"
|
||||
:total-rows="contributionCount"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -13,6 +13,7 @@ describe('ContributionListItem', () => {
|
||||
|
||||
const propsData = {
|
||||
id: 1,
|
||||
createdAt: '26/07/2022',
|
||||
contributionDate: '07/06/2022',
|
||||
memo: 'Ich habe 10 Stunden die Elbwiesen von Müll befreit.',
|
||||
amount: '200',
|
||||
@ -84,21 +85,9 @@ describe('ContributionListItem', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('contribution date', () => {
|
||||
it('is contributionDate by default', () => {
|
||||
expect(wrapper.vm.date).toBe(wrapper.vm.contributionDate)
|
||||
})
|
||||
|
||||
it('is deletedAt when deletedAt is present', async () => {
|
||||
const now = new Date().toISOString()
|
||||
await wrapper.setProps({ deletedAt: now })
|
||||
expect(wrapper.vm.date).toBe(now)
|
||||
})
|
||||
|
||||
it('is confirmedAt at when confirmedAt is present', async () => {
|
||||
const now = new Date().toISOString()
|
||||
await wrapper.setProps({ confirmedAt: now })
|
||||
expect(wrapper.vm.date).toBe(now)
|
||||
describe('date', () => {
|
||||
it('is equal to createdAt', () => {
|
||||
expect(wrapper.vm.date).toBe(wrapper.vm.createdAt)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -11,6 +11,12 @@
|
||||
{{ $t('math.minus') }}
|
||||
<div class="mx-2">{{ $d(new Date(date), 'short') }}</div>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<span>{{ $t('contribution.date') }}</span>
|
||||
<span>
|
||||
{{ $d(new Date(contributionDate), 'monthAndYear') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mr-2">{{ memo }}</div>
|
||||
<div v-if="type === 'pending' && !firstName" class="d-flex flex-row-reverse">
|
||||
<div
|
||||
@ -91,9 +97,10 @@ export default {
|
||||
return 'primary'
|
||||
},
|
||||
date() {
|
||||
if (this.deletedAt) return this.deletedAt
|
||||
if (this.confirmedAt) return this.confirmedAt
|
||||
return this.contributionDate
|
||||
// if (this.deletedAt) return this.deletedAt
|
||||
// if (this.confirmedAt) return this.confirmedAt
|
||||
// return this.contributionDate
|
||||
return this.createdAt
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@ -3,7 +3,13 @@
|
||||
<b-col>
|
||||
<b-card class="p-0 gradido-custom-background">
|
||||
<div class="h3 mb-4">{{ $t('gdd_per_link.created') }}</div>
|
||||
<clipboard-copy :link="link" @show-qr-code-button="showQrCodeButton" />
|
||||
<clipboard-copy
|
||||
:link="link"
|
||||
:amount="amount"
|
||||
:memo="memo"
|
||||
:validUntil="validUntil"
|
||||
@show-qr-code-button="showQrCodeButton"
|
||||
></clipboard-copy>
|
||||
|
||||
<div class="text-center">
|
||||
<figure-qr-code v-if="showQrcode" :link="link" />
|
||||
@ -27,10 +33,10 @@ export default {
|
||||
FigureQrCode,
|
||||
},
|
||||
props: {
|
||||
link: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
link: { type: String, required: true },
|
||||
amount: { type: String, required: true },
|
||||
memo: { type: String, required: true },
|
||||
validUntil: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@ -69,6 +69,7 @@
|
||||
:per-page="pageSize"
|
||||
:total-rows="transactionCount"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
|
||||
<div v-if="transactionCount <= 0" class="mt-4 text-center">
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
:per-page="pageSize"
|
||||
:total-rows="transactionGdtCount"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -17,7 +17,7 @@ const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
hasElopage: false,
|
||||
hasElopage: true,
|
||||
isAdmin: true,
|
||||
},
|
||||
},
|
||||
@ -39,15 +39,17 @@ describe('Navbar', () => {
|
||||
expect(wrapper.find('div.component-navbar').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('navigation Navbar', () => {
|
||||
describe('navigation Navbar (general elements)', () => {
|
||||
it('has .navbar-brand in the navbar', () => {
|
||||
expect(wrapper.find('.navbar-brand').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has b-navbar-toggle in the navbar', () => {
|
||||
expect(wrapper.find('.navbar-toggler').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has ten b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(10)
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(11)
|
||||
})
|
||||
|
||||
it('has first nav-item "amount GDD" in navbar', () => {
|
||||
@ -57,31 +59,57 @@ describe('Navbar', () => {
|
||||
it('has first 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', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(4).text()).toEqual('navigation.send')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.transactions" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.transactions')
|
||||
})
|
||||
it('has first nav-item "navigation.profile" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.profile')
|
||||
|
||||
it('has first nav-item "navigation.transactions" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.community')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.profile" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.profile')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigation Navbar (user has an elopage account)', () => {
|
||||
it('has a link to the members area', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toContain('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(7).find('a').attributes('href')).toBe(
|
||||
expect(wrapper.findAll('.nav-item').at(8).text()).toContain('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(8).find('a').attributes('href')).toBe(
|
||||
'https://elopage.com',
|
||||
)
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(9).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')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigation Navbar (user has no elopage account)', () => {
|
||||
beforeAll(() => {
|
||||
mocks.$store.state.hasElopage = false
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has first 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.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('check watch visible true', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setProps({ visible: true })
|
||||
|
||||
@ -52,17 +52,18 @@
|
||||
<b-icon icon="layout-text-sidebar-reverse" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.transactions') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item to="/community" class="mb-3">
|
||||
<b-icon icon="people" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.community') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item to="/profile" class="mb-3">
|
||||
<b-icon icon="gear" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.profile') }}
|
||||
</b-nav-item>
|
||||
<br />
|
||||
<b-nav-item :href="elopageUri" class="mb-3" target="_blank">
|
||||
<b-nav-item v-if="$store.state.hasElopage" :href="elopageUri" class="mb-3" target="_blank">
|
||||
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.members_area') }}
|
||||
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">
|
||||
{{ $t('math.exclaim') }}
|
||||
</b-badge>
|
||||
</b-nav-item>
|
||||
<b-nav-item class="mb-3" v-if="$store.state.isAdmin" @click="$emit('admin')">
|
||||
<b-icon icon="shield-check" aria-hidden="true"></b-icon>
|
||||
|
||||
@ -27,15 +27,12 @@ describe('Sidebar', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div#component-sidebar').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('navigation Navbar', () => {
|
||||
it('has seven b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(8)
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
@ -55,6 +52,12 @@ describe('Sidebar', () => {
|
||||
it('has first nav-item "navigation.profile" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(4).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(8)
|
||||
})
|
||||
|
||||
it('has a link to the members area', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.members_area')
|
||||
@ -69,5 +72,24 @@ describe('Sidebar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigation Navbar (user has no elopage account)', () => {
|
||||
beforeAll(() => {
|
||||
mocks.$store.state.hasElopage = false
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has seven b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(7)
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -27,12 +27,14 @@
|
||||
</b-nav>
|
||||
<hr />
|
||||
<b-nav vertical class="w-100">
|
||||
<b-nav-item class="mb-3" :href="elopageUri" target="_blank">
|
||||
<b-nav-item
|
||||
v-if="$store.state.hasElopage"
|
||||
class="mb-3"
|
||||
:href="elopageUri"
|
||||
target="_blank"
|
||||
>
|
||||
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.members_area') }}
|
||||
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">
|
||||
{{ $t('math.exclaim') }}
|
||||
</b-badge>
|
||||
</b-nav-item>
|
||||
<b-nav-item class="mb-3" v-if="$store.state.isAdmin" @click="$emit('admin')">
|
||||
<b-icon icon="shield-check" aria-hidden="true"></b-icon>
|
||||
|
||||
@ -18,17 +18,17 @@
|
||||
<b-icon icon="three-dots-vertical"></b-icon>
|
||||
</template>
|
||||
|
||||
<b-dropdown-item v-if="validLink" class="test-copy-link" @click="copy">
|
||||
<b-dropdown-item v-if="validLink" class="test-copy-link" @click="copyLink">
|
||||
<b-icon icon="clipboard"></b-icon>
|
||||
{{ $t('gdd_per_link.copy') }}
|
||||
{{ $t('gdd_per_link.copy-link') }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
v-if="validLink"
|
||||
class="test-copy-text pt-3"
|
||||
@click="copyLinkWithText()"
|
||||
@click="copyLinkWithText"
|
||||
>
|
||||
<b-icon icon="clipboard-plus"></b-icon>
|
||||
{{ $t('gdd_per_link.copy-with-text') }}
|
||||
{{ $t('gdd_per_link.copy-link-with-text') }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
v-if="validLink"
|
||||
@ -76,9 +76,11 @@ import MemoRow from '../TransactionRows/MemoRow'
|
||||
import DateRow from '../TransactionRows/DateRow'
|
||||
import DecayRow from '../TransactionRows/DecayRow'
|
||||
import FigureQrCode from '@/components/QrCode/FigureQrCode.vue'
|
||||
import { copyLinks } from '../../mixins/copyLinks'
|
||||
|
||||
export default {
|
||||
name: 'TransactionLink',
|
||||
mixins: [copyLinks],
|
||||
components: {
|
||||
TypeIcon,
|
||||
AmountAndNameRow,
|
||||
@ -88,43 +90,10 @@ export default {
|
||||
FigureQrCode,
|
||||
},
|
||||
props: {
|
||||
amount: { type: String, required: true },
|
||||
link: { type: String, required: true },
|
||||
holdAvailableAmount: { type: String, required: true },
|
||||
id: { type: Number, required: true },
|
||||
memo: { type: String, required: true },
|
||||
validUntil: { type: String, required: true },
|
||||
},
|
||||
methods: {
|
||||
copy() {
|
||||
navigator.clipboard
|
||||
.writeText(this.link)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-copied'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.$bvModal.show('modalPopoverCopyError' + this.id)
|
||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||
})
|
||||
},
|
||||
copyLinkWithText() {
|
||||
navigator.clipboard
|
||||
.writeText(
|
||||
`${this.link}
|
||||
${this.$store.state.firstName} ${this.$t('transaction-link.send_you')} ${this.amount} Gradido.
|
||||
"${this.memo}"
|
||||
${this.$t('gdd_per_link.credit-your-gradido')} ${this.$t('gdd_per_link.validUntilDate', {
|
||||
date: this.$d(new Date(this.validUntil), 'short'),
|
||||
})}`,
|
||||
)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-and-text-copied'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.$bvModal.show('modalPopoverCopyError' + this.id)
|
||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||
})
|
||||
},
|
||||
deleteLink() {
|
||||
this.$bvModal.msgBoxConfirm(this.$t('gdd_per_link.delete-the-link')).then(async (value) => {
|
||||
if (value)
|
||||
|
||||
@ -74,6 +74,9 @@ export const createTransactionLink = gql`
|
||||
mutation($amount: Decimal!, $memo: String!) {
|
||||
createTransactionLink(amount: $amount, memo: $memo) {
|
||||
link
|
||||
amount
|
||||
memo
|
||||
validUntil
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
"community": "Gemeinschaft",
|
||||
"continue-to-registration": "Weiter zur Registrierung",
|
||||
"current-community": "Aktuelle Gemeinschaft",
|
||||
"myContributions": "Meine Beiträge",
|
||||
"myContributions": "Meine Beiträge zum Gemeinwohl",
|
||||
"other-communities": "Weitere Gemeinschaften",
|
||||
"submitContribution": "Beitrag einreichen",
|
||||
"switch-to-this-community": "zu dieser Gemeinschaft wechseln"
|
||||
@ -36,11 +36,12 @@
|
||||
"alert": {
|
||||
"communityNoteList": "Hier findest du alle eingereichten und bestätigten Beiträge von allen Mitgliedern aus dieser Gemeinschaft.",
|
||||
"confirm": "bestätigt",
|
||||
"myContributionNoteList": "Hier findest du chronologisch aufgelistet alle deine eingereichten Beiträge. Es gibt drei Darstellungsarten. Du kannst deine Beiträge, welche noch nicht bestätigt wurden, jederzeit bearbeiten.",
|
||||
"myContributionNoteList": "Eingereichte Beiträge, die noch nicht bestätigt wurden, kannst du jederzeit bearbeiten oder löschen.",
|
||||
"myContributionNoteSupport": "Es wird bald an dieser Stelle die Möglichkeit geben das ein Dialog zwischen Moderatoren und dir stattfinden kann. Solltest du jetzt Probleme haben bitte nimm Kontakt mit dem Support auf.",
|
||||
"pending": "Eingereicht und wartet auf Bestätigung",
|
||||
"rejected": "abgelehnt"
|
||||
},
|
||||
"date": "Beitrag für:",
|
||||
"delete": "Beitrag löschen! Bist du sicher?",
|
||||
"deleted": "Der Beitrag wurde gelöscht! Wird aber sichtbar bleiben.",
|
||||
"formText": {
|
||||
@ -54,7 +55,8 @@
|
||||
"selectDate": "Wann war dein Beitrag?",
|
||||
"submit": "Einreichen",
|
||||
"submitted": "Der Beitrag wurde eingereicht.",
|
||||
"updated": "Der Beitrag wurde geändert."
|
||||
"updated": "Der Beitrag wurde geändert.",
|
||||
"yourActivity": "Bitte trage eine Tätigkeit ein!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "dankt dir mit"
|
||||
@ -105,17 +107,18 @@
|
||||
"amount": "Betrag",
|
||||
"at": "am",
|
||||
"cancel": "Abbrechen",
|
||||
"change": "Ändern",
|
||||
"check_now": "Jetzt prüfen",
|
||||
"close": "Schließen",
|
||||
"current_balance": "Aktueller Kontostand",
|
||||
"date": "Datum",
|
||||
"description": "Beschreibung",
|
||||
"edit": "Bearbeiten",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Vorname",
|
||||
"from": "Von",
|
||||
"generate_now": "Jetzt generieren",
|
||||
"lastname": "Nachname",
|
||||
"mandatoryField": "Pflichtfeld",
|
||||
"memo": "Nachricht",
|
||||
"message": "Nachricht",
|
||||
"new_balance": "Neuer Kontostand nach Bestätigung",
|
||||
@ -150,8 +153,8 @@
|
||||
"GDD": "GDD",
|
||||
"gdd_per_link": {
|
||||
"choose-amount": "Wähle einen Betrag aus, welchen du per Link versenden möchtest. Du kannst auch noch eine Nachricht eintragen. Beim Klick „Jetzt generieren“ wird ein Link erstellt, den du versenden kannst.",
|
||||
"copy": "kopieren",
|
||||
"copy-with-text": "Link und Text kopieren",
|
||||
"copy-link": "Link kopieren",
|
||||
"copy-link-with-text": "Link und Text kopieren",
|
||||
"created": "Der Link wurde erstellt!",
|
||||
"credit-your-gradido": "Damit die Gradido gutgeschrieben werden können, klicke auf den Link!",
|
||||
"decay-14-day": "Vergänglichkeit für 14 Tage",
|
||||
@ -203,10 +206,8 @@
|
||||
"login": "Anmeldung",
|
||||
"math": {
|
||||
"aprox": "~",
|
||||
"divide": "/",
|
||||
"asterisk": "*",
|
||||
"equal": "=",
|
||||
"exclaim": "!",
|
||||
"lower": "<",
|
||||
"minus": "−",
|
||||
"pipe": "|"
|
||||
},
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
"community": "Community",
|
||||
"continue-to-registration": "Continue to registration",
|
||||
"current-community": "Current community",
|
||||
"myContributions": "My contributions",
|
||||
"myContributions": "My contributions to the common good",
|
||||
"other-communities": "Other communities",
|
||||
"submitContribution": "Submit contribution",
|
||||
"switch-to-this-community": "Switch to this community"
|
||||
@ -36,11 +36,12 @@
|
||||
"alert": {
|
||||
"communityNoteList": "Here you will find all submitted and confirmed contributions from all members of this community.",
|
||||
"confirm": "confirmed",
|
||||
"myContributionNoteList": "Here you will find a chronological list of all your submitted contributions. There are three display types. There are three ways of displaying your posts. You can edit your contributions, which have not yet been confirmed, at any time.",
|
||||
"myContributionNoteList": "You can edit or delete entries that have not yet been confirmed at any time.",
|
||||
"myContributionNoteSupport": "Soon there will be the possibility for a dialogue between moderators and you. If you have any problems now, please contact the support.",
|
||||
"pending": "Submitted and waiting for confirmation",
|
||||
"rejected": "deleted"
|
||||
},
|
||||
"date": "Contribution for:",
|
||||
"delete": "Delete Contribution! Are you sure?",
|
||||
"deleted": "The contribution has been deleted! But it will remain visible.",
|
||||
"formText": {
|
||||
@ -54,7 +55,8 @@
|
||||
"selectDate": "When was your contribution?",
|
||||
"submit": "Submit",
|
||||
"submitted": "The contribution was submitted.",
|
||||
"updated": "The contribution was changed."
|
||||
"updated": "The contribution was changed.",
|
||||
"yourActivity": "Please enter an activity!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "thanks you with"
|
||||
@ -105,17 +107,18 @@
|
||||
"amount": "Amount",
|
||||
"at": "at",
|
||||
"cancel": "Cancel",
|
||||
"change": "Change",
|
||||
"check_now": "Check now",
|
||||
"close": "Close",
|
||||
"current_balance": "Current Balance",
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
"edit": "Edit",
|
||||
"email": "Email",
|
||||
"firstname": "Firstname",
|
||||
"from": "from",
|
||||
"generate_now": "Generate now",
|
||||
"lastname": "Lastname",
|
||||
"mandatoryField": "mandatory field",
|
||||
"memo": "Message",
|
||||
"message": "Message",
|
||||
"new_balance": "Account balance after confirmation",
|
||||
@ -150,8 +153,8 @@
|
||||
"GDD": "GDD",
|
||||
"gdd_per_link": {
|
||||
"choose-amount": "Select an amount that you would like to send via link. You can also enter a message. Click 'Generate now' to create a link that you can share.",
|
||||
"copy": "copy",
|
||||
"copy-with-text": "Copy link and text",
|
||||
"copy-link": "Copy link",
|
||||
"copy-link-with-text": "Copy link and text",
|
||||
"created": "Link was created!",
|
||||
"credit-your-gradido": "For the Gradido to be credited, click on the link!",
|
||||
"decay-14-day": "Decay for 14 days",
|
||||
@ -203,10 +206,8 @@
|
||||
"login": "Login",
|
||||
"math": {
|
||||
"aprox": "~",
|
||||
"divide": "/",
|
||||
"asterisk": "*",
|
||||
"equal": "=",
|
||||
"exclaim": "!",
|
||||
"lower": "<",
|
||||
"minus": "−",
|
||||
"pipe": "|"
|
||||
},
|
||||
|
||||
44
frontend/src/mixins/copyLinks.js
Normal file
44
frontend/src/mixins/copyLinks.js
Normal file
@ -0,0 +1,44 @@
|
||||
export const copyLinks = {
|
||||
props: {
|
||||
link: { type: String, required: true },
|
||||
amount: { type: String, required: true },
|
||||
memo: { type: String, required: true },
|
||||
validUntil: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
canCopyLink: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copyLink() {
|
||||
navigator.clipboard
|
||||
.writeText(this.link)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-copied'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.canCopyLink = false
|
||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||
})
|
||||
},
|
||||
copyLinkWithText() {
|
||||
navigator.clipboard
|
||||
.writeText(
|
||||
`${this.link}
|
||||
${this.$store.state.firstName} ${this.$t('transaction-link.send_you')} ${this.amount} Gradido.
|
||||
"${this.memo}"
|
||||
${this.$t('gdd_per_link.credit-your-gradido')} ${this.$t('gdd_per_link.validUntilDate', {
|
||||
date: this.$d(new Date(this.validUntil), 'short'),
|
||||
})}`,
|
||||
)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-and-text-copied'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.canCopyLink = false
|
||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -26,6 +26,9 @@ describe('Community', () => {
|
||||
creation: ['1000', '1000', '1000'],
|
||||
},
|
||||
},
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
@ -190,6 +193,13 @@ describe('Community', () => {
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
})
|
||||
|
||||
it('set all data to the default values)', () => {
|
||||
expect(wrapper.vm.form.id).toBe(null)
|
||||
expect(wrapper.vm.form.date).toBe('')
|
||||
expect(wrapper.vm.form.memo).toBe('')
|
||||
expect(wrapper.vm.form.amount).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with error', () => {
|
||||
@ -270,6 +280,13 @@ describe('Community', () => {
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
})
|
||||
|
||||
it('set all data to the default values)', () => {
|
||||
expect(wrapper.vm.form.id).toBe(null)
|
||||
expect(wrapper.vm.form.date).toBe('')
|
||||
expect(wrapper.vm.form.memo).toBe('')
|
||||
expect(wrapper.vm.form.amount).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with error', () => {
|
||||
@ -376,7 +393,7 @@ describe('Community', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('sets the form date to the new values', () => {
|
||||
it('sets the form data to the new values', () => {
|
||||
expect(wrapper.vm.form.id).toBe(2)
|
||||
expect(wrapper.vm.form.date).toBe(now)
|
||||
expect(wrapper.vm.form.memo).toBe('Mein Beitrag zur Gemeinschaft für diesen Monat ...')
|
||||
|
||||
@ -25,9 +25,11 @@ describe('Send', () => {
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$n: jest.fn((n) => String(n)),
|
||||
$d: jest.fn((d) => d),
|
||||
$store: {
|
||||
state: {
|
||||
email: 'sender@example.org',
|
||||
firstName: 'Testy',
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
@ -160,11 +162,15 @@ describe('Send', () => {
|
||||
})
|
||||
|
||||
describe('transaction form link', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
apolloMutationMock.mockResolvedValue({
|
||||
data: {
|
||||
createTransactionLink: {
|
||||
link: 'http://localhost/redeem/0123456789',
|
||||
amount: '56.78',
|
||||
memo: 'Make the best of the link!',
|
||||
validUntil: now,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -228,18 +234,64 @@ describe('Send', () => {
|
||||
navigator.clipboard = navigatorClipboard
|
||||
})
|
||||
|
||||
describe('copy with success', () => {
|
||||
describe('copy link with success', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockResolvedValue()
|
||||
await wrapper.findAll('button').at(0).trigger('click')
|
||||
await wrapper.findAll('button').at(1).trigger('click')
|
||||
})
|
||||
|
||||
it('should call clipboard.writeText', () => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
||||
'http://localhost/redeem/0123456789',
|
||||
)
|
||||
})
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.link-copied')
|
||||
})
|
||||
})
|
||||
|
||||
describe('copy with error', () => {
|
||||
describe('copy link with error', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockRejectedValue()
|
||||
await wrapper.findAll('button').at(1).trigger('click')
|
||||
})
|
||||
|
||||
it('toasts error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('gdd_per_link.not-copied')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('copy link and text with success', () => {
|
||||
const navigatorClipboard = navigator.clipboard
|
||||
beforeAll(() => {
|
||||
delete navigator.clipboard
|
||||
navigator.clipboard = { writeText: navigatorClipboardMock }
|
||||
})
|
||||
afterAll(() => {
|
||||
navigator.clipboard = navigatorClipboard
|
||||
})
|
||||
|
||||
describe('copy link and text with success', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockResolvedValue()
|
||||
await wrapper.findAll('button').at(0).trigger('click')
|
||||
})
|
||||
|
||||
it('should call clipboard.writeText', () => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
||||
'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',
|
||||
)
|
||||
})
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.link-and-text-copied')
|
||||
})
|
||||
})
|
||||
|
||||
describe('copy link and text with error', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockRejectedValue()
|
||||
await wrapper.findAll('button').at(0).trigger('click')
|
||||
@ -253,7 +305,7 @@ describe('Send', () => {
|
||||
|
||||
describe('close button click', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findAll('button').at(2).trigger('click')
|
||||
await wrapper.findAll('button').at(3).trigger('click')
|
||||
})
|
||||
|
||||
it('Shows the TransactionForm', () => {
|
||||
|
||||
@ -41,7 +41,13 @@
|
||||
></transaction-result-send-error>
|
||||
</template>
|
||||
<template #transactionResultLink>
|
||||
<transaction-result-link :link="link" @on-reset="onReset"></transaction-result-link>
|
||||
<transaction-result-link
|
||||
:link="link"
|
||||
:amount="amount"
|
||||
:memo="memo"
|
||||
:validUntil="validUntil"
|
||||
@on-reset="onReset"
|
||||
></transaction-result-link>
|
||||
</template>
|
||||
</gdd-send>
|
||||
<hr />
|
||||
@ -144,7 +150,15 @@ export default {
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('set-tunneled-email', null)
|
||||
this.link = result.data.createTransactionLink.link
|
||||
const {
|
||||
data: {
|
||||
createTransactionLink: { link, amount, memo, validUntil },
|
||||
},
|
||||
} = result
|
||||
this.link = link
|
||||
this.amount = amount
|
||||
this.memo = memo
|
||||
this.validUntil = validUntil
|
||||
this.transactionData = { ...EMPTY_TRANSACTION_DATA }
|
||||
this.currentTransactionStep = TRANSACTION_STEPS.transactionResultLink
|
||||
this.updateTransactions({})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"description": "Gradido",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:gradido/gradido.git",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user