mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into adminCreateMultiplePendingCreations
This commit is contained in:
commit
04bea42261
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@ -380,7 +380,7 @@ jobs:
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
- name: frontend | Unit tests
|
- name: frontend | Unit tests
|
||||||
run: |
|
run: |
|
||||||
docker run -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test
|
docker run --env NODE_ENV=test -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test
|
||||||
cp -r ~/coverage ./coverage
|
cp -r ~/coverage ./coverage
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# COVERAGE REPORT FRONTEND ###############################################
|
# COVERAGE REPORT FRONTEND ###############################################
|
||||||
@ -399,7 +399,7 @@ jobs:
|
|||||||
report_name: Coverage Frontend
|
report_name: Coverage Frontend
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./coverage/lcov.info
|
||||||
min_coverage: 90
|
min_coverage: 94
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
@ -441,7 +441,7 @@ jobs:
|
|||||||
report_name: Coverage Admin Interface
|
report_name: Coverage Admin Interface
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./coverage/lcov.info
|
||||||
min_coverage: 69
|
min_coverage: 76
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import Creation from './Creation.vue'
|
import Creation from './Creation.vue'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
@ -7,11 +8,19 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
|||||||
data: {
|
data: {
|
||||||
searchUsers: [
|
searchUsers: [
|
||||||
{
|
{
|
||||||
|
id: 1,
|
||||||
firstName: 'Bibi',
|
firstName: 'Bibi',
|
||||||
lastName: 'Bloxberg',
|
lastName: 'Bloxberg',
|
||||||
email: 'bibi@bloxberg.de',
|
email: 'bibi@bloxberg.de',
|
||||||
creation: [200, 400, 600],
|
creation: [200, 400, 600],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Benjamin',
|
||||||
|
lastName: 'Blümchen',
|
||||||
|
email: 'benjamin@bluemchen.de',
|
||||||
|
creation: [800, 600, 400],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -31,10 +40,10 @@ describe('Creation', () => {
|
|||||||
let wrapper
|
let wrapper
|
||||||
|
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
return mount(Creation, { localVue, mocks })
|
return shallowMount(Creation, { localVue, mocks })
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('shallowMount', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
@ -43,6 +52,170 @@ describe('Creation', () => {
|
|||||||
expect(wrapper.find('div.creation').exists()).toBeTruthy()
|
expect(wrapper.find('div.creation').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('apollo returns user array', () => {
|
||||||
|
it('calls the searchUser query', () => {
|
||||||
|
expect(apolloQueryMock).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the data of itemsList', () => {
|
||||||
|
expect(wrapper.vm.itemsList).toEqual([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
creation: [200, 400, 600],
|
||||||
|
showDetails: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Benjamin',
|
||||||
|
lastName: 'Blümchen',
|
||||||
|
email: 'benjamin@bluemchen.de',
|
||||||
|
creation: [800, 600, 400],
|
||||||
|
showDetails: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('update item', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('push', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper.findComponent({ name: 'UserTable' }).vm.$emit(
|
||||||
|
'update-item',
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Benjamin',
|
||||||
|
lastName: 'Blümchen',
|
||||||
|
email: 'benjamin@bluemchen.de',
|
||||||
|
creation: [800, 600, 400],
|
||||||
|
showDetails: false,
|
||||||
|
},
|
||||||
|
'push',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes the pushed item from itemsList', () => {
|
||||||
|
expect(wrapper.vm.itemsList).toEqual([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
creation: [200, 400, 600],
|
||||||
|
showDetails: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds the pushed item to itemsMassCreation', () => {
|
||||||
|
expect(wrapper.vm.itemsMassCreation).toEqual([
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Benjamin',
|
||||||
|
lastName: 'Blümchen',
|
||||||
|
email: 'benjamin@bluemchen.de',
|
||||||
|
creation: [800, 600, 400],
|
||||||
|
showDetails: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('remove', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper.findComponent({ name: 'UserTable' }).vm.$emit(
|
||||||
|
'update-item',
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Benjamin',
|
||||||
|
lastName: 'Blümchen',
|
||||||
|
email: 'benjamin@bluemchen.de',
|
||||||
|
creation: [800, 600, 400],
|
||||||
|
showDetails: false,
|
||||||
|
},
|
||||||
|
'remove',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes the item from itemsMassCreation', () => {
|
||||||
|
expect(wrapper.vm.itemsMassCreation).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds the item to itemsList', () => {
|
||||||
|
expect(wrapper.vm.itemsList).toEqual([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
creation: [200, 400, 600],
|
||||||
|
showDetails: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Benjamin',
|
||||||
|
lastName: 'Blümchen',
|
||||||
|
email: 'benjamin@bluemchen.de',
|
||||||
|
creation: [800, 600, 400],
|
||||||
|
showDetails: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('error', () => {
|
||||||
|
const consoleErrorMock = jest.fn()
|
||||||
|
const warnHandler = Vue.config.warnHandler
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
Vue.config.warnHandler = (w) => {}
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error = consoleErrorMock
|
||||||
|
wrapper.findComponent({ name: 'UserTable' }).vm.$emit('update-item', {}, 'no-rule')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Vue.config.warnHandler = warnHandler
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error', () => {
|
||||||
|
expect(consoleErrorMock).toBeCalledWith(expect.objectContaining({ message: 'no-rule' }))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('remove all bookmarks', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findComponent({ name: 'UserTable' }).vm.$emit(
|
||||||
|
'update-item',
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Benjamin',
|
||||||
|
lastName: 'Blümchen',
|
||||||
|
email: 'benjamin@bluemchen.de',
|
||||||
|
creation: [800, 600, 400],
|
||||||
|
showDetails: false,
|
||||||
|
},
|
||||||
|
'push',
|
||||||
|
)
|
||||||
|
wrapper.findComponent({ name: 'CreationFormular' }).vm.$emit('remove-all-bookmark')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes all items from itemsMassCreation', () => {
|
||||||
|
expect(wrapper.vm.itemsMassCreation).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds all items to itemsList', () => {
|
||||||
|
expect(wrapper.vm.itemsList).toHaveLength(2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('apollo returns error', () => {
|
describe('apollo returns error', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
apolloQueryMock.mockRejectedValue({
|
apolloQueryMock.mockRejectedValue({
|
||||||
|
|||||||
@ -125,20 +125,9 @@ export default {
|
|||||||
throw new Error(event)
|
throw new Error(event)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// updateRadioSelected(obj) {
|
|
||||||
// this.radioSelectedMass = obj[0]
|
|
||||||
// },
|
|
||||||
|
|
||||||
removeAllBookmark() {
|
removeAllBookmark() {
|
||||||
alert('remove all bookmarks')
|
this.itemsMassCreation.forEach((item) => this.itemsList.push(item))
|
||||||
const index = 0
|
this.itemsMassCreation = []
|
||||||
let i = 0
|
|
||||||
|
|
||||||
for (i; i < this.itemsMassCreation.length; i++) {
|
|
||||||
this.itemsList.push(this.itemsMassCreation[i])
|
|
||||||
}
|
|
||||||
this.itemsMassCreation.splice(index, this.itemsMassCreation.length)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { UpdatePendingCreation } from '../model/UpdatePendingCreation'
|
|||||||
import { RIGHTS } from '../../auth/RIGHTS'
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
import { TransactionRepository } from '../../typeorm/repository/Transaction'
|
import { TransactionRepository } from '../../typeorm/repository/Transaction'
|
||||||
import { TransactionCreationRepository } from '../../typeorm/repository/TransactionCreation'
|
import { TransactionCreationRepository } from '../../typeorm/repository/TransactionCreation'
|
||||||
import { PendingCreationRepository } from '../../typeorm/repository/PendingCreation'
|
import { LoginPendingTasksAdminRepository } from '../../typeorm/repository/LoginPendingTasksAdmin'
|
||||||
import { UserRepository } from '../../typeorm/repository/User'
|
import { UserRepository } from '../../typeorm/repository/User'
|
||||||
import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
|
import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
|
||||||
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
|
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
|
||||||
@ -17,6 +17,7 @@ import { TransactionCreation } from '@entity/TransactionCreation'
|
|||||||
import { UserTransaction } from '@entity/UserTransaction'
|
import { UserTransaction } from '@entity/UserTransaction'
|
||||||
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
|
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
|
||||||
import { BalanceRepository } from '../../typeorm/repository/Balance'
|
import { BalanceRepository } from '../../typeorm/repository/Balance'
|
||||||
|
import { calculateDecay } from '../../util/decay'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class AdminResolver {
|
export class AdminResolver {
|
||||||
@ -49,8 +50,8 @@ export class AdminResolver {
|
|||||||
const creations = await getUserCreations(user.id)
|
const creations = await getUserCreations(user.id)
|
||||||
const creationDateObj = new Date(creationDate)
|
const creationDateObj = new Date(creationDate)
|
||||||
if (isCreationValid(creations, amount, creationDateObj)) {
|
if (isCreationValid(creations, amount, creationDateObj)) {
|
||||||
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
const loginPendingTasksAdminRepository = getCustomRepository(LoginPendingTasksAdminRepository)
|
||||||
const loginPendingTaskAdmin = pendingCreationRepository.create()
|
const loginPendingTaskAdmin = loginPendingTasksAdminRepository.create()
|
||||||
loginPendingTaskAdmin.userId = user.id
|
loginPendingTaskAdmin.userId = user.id
|
||||||
loginPendingTaskAdmin.amount = BigInt(amount * 10000)
|
loginPendingTaskAdmin.amount = BigInt(amount * 10000)
|
||||||
loginPendingTaskAdmin.created = new Date()
|
loginPendingTaskAdmin.created = new Date()
|
||||||
@ -58,7 +59,7 @@ export class AdminResolver {
|
|||||||
loginPendingTaskAdmin.memo = memo
|
loginPendingTaskAdmin.memo = memo
|
||||||
loginPendingTaskAdmin.moderator = moderator
|
loginPendingTaskAdmin.moderator = moderator
|
||||||
|
|
||||||
pendingCreationRepository.save(loginPendingTaskAdmin)
|
loginPendingTasksAdminRepository.save(loginPendingTaskAdmin)
|
||||||
}
|
}
|
||||||
return await getUserCreations(user.id)
|
return await getUserCreations(user.id)
|
||||||
}
|
}
|
||||||
@ -130,8 +131,8 @@ export class AdminResolver {
|
|||||||
const userRepository = getCustomRepository(UserRepository)
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
const user = await userRepository.findByEmail(email)
|
const user = await userRepository.findByEmail(email)
|
||||||
|
|
||||||
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
const loginPendingTasksAdminRepository = getCustomRepository(LoginPendingTasksAdminRepository)
|
||||||
const updatedCreation = await pendingCreationRepository.findOneOrFail({ id })
|
const updatedCreation = await loginPendingTasksAdminRepository.findOneOrFail({ id })
|
||||||
|
|
||||||
if (updatedCreation.userId !== user.id)
|
if (updatedCreation.userId !== user.id)
|
||||||
throw new Error('user of the pending creation and send user does not correspond')
|
throw new Error('user of the pending creation and send user does not correspond')
|
||||||
@ -141,9 +142,9 @@ export class AdminResolver {
|
|||||||
updatedCreation.date = new Date(creationDate)
|
updatedCreation.date = new Date(creationDate)
|
||||||
updatedCreation.moderator = moderator
|
updatedCreation.moderator = moderator
|
||||||
|
|
||||||
await pendingCreationRepository.save(updatedCreation)
|
await loginPendingTasksAdminRepository.save(updatedCreation)
|
||||||
const result = new UpdatePendingCreation()
|
const result = new UpdatePendingCreation()
|
||||||
result.amount = parseInt(updatedCreation.amount.toString())
|
result.amount = parseInt(amount.toString())
|
||||||
result.memo = updatedCreation.memo
|
result.memo = updatedCreation.memo
|
||||||
result.date = updatedCreation.date
|
result.date = updatedCreation.date
|
||||||
result.moderator = updatedCreation.moderator
|
result.moderator = updatedCreation.moderator
|
||||||
@ -154,8 +155,8 @@ export class AdminResolver {
|
|||||||
|
|
||||||
@Query(() => [PendingCreation])
|
@Query(() => [PendingCreation])
|
||||||
async getPendingCreations(): Promise<PendingCreation[]> {
|
async getPendingCreations(): Promise<PendingCreation[]> {
|
||||||
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
const loginPendingTasksAdminRepository = getCustomRepository(LoginPendingTasksAdminRepository)
|
||||||
const pendingCreations = await pendingCreationRepository.find()
|
const pendingCreations = await loginPendingTasksAdminRepository.find()
|
||||||
|
|
||||||
const pendingCreationsPromise = await Promise.all(
|
const pendingCreationsPromise = await Promise.all(
|
||||||
pendingCreations.map(async (pendingCreation) => {
|
pendingCreations.map(async (pendingCreation) => {
|
||||||
@ -181,22 +182,23 @@ export class AdminResolver {
|
|||||||
|
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async deletePendingCreation(@Arg('id') id: number): Promise<boolean> {
|
async deletePendingCreation(@Arg('id') id: number): Promise<boolean> {
|
||||||
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
const loginPendingTasksAdminRepository = getCustomRepository(LoginPendingTasksAdminRepository)
|
||||||
const entity = await pendingCreationRepository.findOneOrFail(id)
|
const entity = await loginPendingTasksAdminRepository.findOneOrFail(id)
|
||||||
const res = await pendingCreationRepository.delete(entity)
|
const res = await loginPendingTasksAdminRepository.delete(entity)
|
||||||
return !!res
|
return !!res
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async confirmPendingCreation(@Arg('id') id: number): Promise<boolean> {
|
async confirmPendingCreation(@Arg('id') id: number): Promise<boolean> {
|
||||||
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
const loginPendingTasksAdminRepository = getCustomRepository(LoginPendingTasksAdminRepository)
|
||||||
const pendingCreation = await pendingCreationRepository.findOneOrFail(id)
|
const pendingCreation = await loginPendingTasksAdminRepository.findOneOrFail(id)
|
||||||
|
|
||||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||||
|
const receivedCallDate = new Date()
|
||||||
let transaction = new Transaction()
|
let transaction = new Transaction()
|
||||||
transaction.transactionTypeId = 1
|
transaction.transactionTypeId = 1
|
||||||
transaction.memo = pendingCreation.memo
|
transaction.memo = pendingCreation.memo
|
||||||
transaction.received = new Date()
|
transaction.received = receivedCallDate
|
||||||
transaction.blockchainTypeId = 1
|
transaction.blockchainTypeId = 1
|
||||||
transaction = await transactionRepository.save(transaction)
|
transaction = await transactionRepository.save(transaction)
|
||||||
if (!transaction) throw new Error('Could not create transaction')
|
if (!transaction) throw new Error('Could not create transaction')
|
||||||
@ -218,9 +220,13 @@ export class AdminResolver {
|
|||||||
if (!lastUserTransaction) {
|
if (!lastUserTransaction) {
|
||||||
newBalance = 0
|
newBalance = 0
|
||||||
} else {
|
} else {
|
||||||
newBalance = lastUserTransaction.balance
|
newBalance = await calculateDecay(
|
||||||
|
lastUserTransaction.balance,
|
||||||
|
lastUserTransaction.balanceDate,
|
||||||
|
receivedCallDate,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()) / 10000)
|
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()))
|
||||||
|
|
||||||
const newUserTransaction = new UserTransaction()
|
const newUserTransaction = new UserTransaction()
|
||||||
newUserTransaction.userId = pendingCreation.userId
|
newUserTransaction.userId = pendingCreation.userId
|
||||||
@ -238,11 +244,11 @@ export class AdminResolver {
|
|||||||
|
|
||||||
if (!userBalance) userBalance = balanceRepository.create()
|
if (!userBalance) userBalance = balanceRepository.create()
|
||||||
userBalance.userId = pendingCreation.userId
|
userBalance.userId = pendingCreation.userId
|
||||||
userBalance.amount = Number(newBalance * 10000)
|
userBalance.amount = Number(newBalance)
|
||||||
userBalance.modified = new Date()
|
userBalance.modified = receivedCallDate
|
||||||
userBalance.recordDate = userBalance.recordDate ? userBalance.recordDate : new Date()
|
userBalance.recordDate = receivedCallDate
|
||||||
await balanceRepository.save(userBalance)
|
await balanceRepository.save(userBalance)
|
||||||
await pendingCreationRepository.delete(pendingCreation)
|
await loginPendingTasksAdminRepository.delete(pendingCreation)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -250,95 +256,80 @@ export class AdminResolver {
|
|||||||
|
|
||||||
async function getUserCreations(id: number): Promise<number[]> {
|
async function getUserCreations(id: number): Promise<number[]> {
|
||||||
const dateNextMonth = moment().add(1, 'month').format('YYYY-MM') + '-01'
|
const dateNextMonth = moment().add(1, 'month').format('YYYY-MM') + '-01'
|
||||||
const dateMonth = moment().format('YYYY-MM') + '-01'
|
|
||||||
const dateLastMonth = moment().subtract(1, 'month').format('YYYY-MM') + '-01'
|
|
||||||
const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM') + '-01'
|
const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM') + '-01'
|
||||||
|
const beforeLastMonthNumber = moment().subtract(2, 'month').format('M')
|
||||||
|
const lastMonthNumber = moment().subtract(1, 'month').format('M')
|
||||||
|
const currentMonthNumber = moment().format('M')
|
||||||
|
|
||||||
const transactionCreationRepository = getCustomRepository(TransactionCreationRepository)
|
const transactionCreationRepository = getCustomRepository(TransactionCreationRepository)
|
||||||
const createdAmountBeforeLastMonth = await transactionCreationRepository
|
const createdAmountsQuery = await transactionCreationRepository
|
||||||
.createQueryBuilder('transaction_creations')
|
.createQueryBuilder('transaction_creations')
|
||||||
.select('SUM(transaction_creations.amount)', 'sum')
|
.select('MONTH(transaction_creations.target_date)', 'target_month')
|
||||||
|
.addSelect('SUM(transaction_creations.amount)', 'sum')
|
||||||
.where('transaction_creations.state_user_id = :id', { id })
|
.where('transaction_creations.state_user_id = :id', { id })
|
||||||
.andWhere({
|
.andWhere({
|
||||||
targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :endDate`, {
|
||||||
date: dateBeforeLastMonth,
|
date: dateBeforeLastMonth,
|
||||||
enddate: dateLastMonth,
|
endDate: dateNextMonth,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.getRawOne()
|
.groupBy('target_month')
|
||||||
|
.orderBy('target_month', 'ASC')
|
||||||
|
.getRawMany()
|
||||||
|
|
||||||
const createdAmountLastMonth = await transactionCreationRepository
|
const loginPendingTasksAdminRepository = getCustomRepository(LoginPendingTasksAdminRepository)
|
||||||
.createQueryBuilder('transaction_creations')
|
const pendingAmountsQuery = await loginPendingTasksAdminRepository
|
||||||
.select('SUM(transaction_creations.amount)', 'sum')
|
|
||||||
.where('transaction_creations.state_user_id = :id', { id })
|
|
||||||
.andWhere({
|
|
||||||
targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
|
||||||
date: dateLastMonth,
|
|
||||||
enddate: dateMonth,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.getRawOne()
|
|
||||||
|
|
||||||
const createdAmountMonth = await transactionCreationRepository
|
|
||||||
.createQueryBuilder('transaction_creations')
|
|
||||||
.select('SUM(transaction_creations.amount)', 'sum')
|
|
||||||
.where('transaction_creations.state_user_id = :id', { id })
|
|
||||||
.andWhere({
|
|
||||||
targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
|
||||||
date: dateMonth,
|
|
||||||
enddate: dateNextMonth,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.getRawOne()
|
|
||||||
|
|
||||||
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
|
||||||
const pendingAmountMounth = await pendingCreationRepository
|
|
||||||
.createQueryBuilder('login_pending_tasks_admin')
|
.createQueryBuilder('login_pending_tasks_admin')
|
||||||
.select('SUM(login_pending_tasks_admin.amount)', 'sum')
|
.select('MONTH(login_pending_tasks_admin.date)', 'target_month')
|
||||||
|
.addSelect('SUM(login_pending_tasks_admin.amount)', 'sum')
|
||||||
.where('login_pending_tasks_admin.userId = :id', { id })
|
.where('login_pending_tasks_admin.userId = :id', { id })
|
||||||
.andWhere({
|
.andWhere({
|
||||||
date: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
date: Raw((alias) => `${alias} >= :date and ${alias} < :endDate`, {
|
||||||
date: dateMonth,
|
|
||||||
enddate: dateNextMonth,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.getRawOne()
|
|
||||||
|
|
||||||
const pendingAmountLastMounth = await pendingCreationRepository
|
|
||||||
.createQueryBuilder('login_pending_tasks_admin')
|
|
||||||
.select('SUM(login_pending_tasks_admin.amount)', 'sum')
|
|
||||||
.where('login_pending_tasks_admin.userId = :id', { id })
|
|
||||||
.andWhere({
|
|
||||||
date: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
|
||||||
date: dateLastMonth,
|
|
||||||
enddate: dateMonth,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.getRawOne()
|
|
||||||
|
|
||||||
const pendingAmountBeforeLastMounth = await pendingCreationRepository
|
|
||||||
.createQueryBuilder('login_pending_tasks_admin')
|
|
||||||
.select('SUM(login_pending_tasks_admin.amount)', 'sum')
|
|
||||||
.where('login_pending_tasks_admin.userId = :id', { id })
|
|
||||||
.andWhere({
|
|
||||||
date: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
|
||||||
date: dateBeforeLastMonth,
|
date: dateBeforeLastMonth,
|
||||||
enddate: dateLastMonth,
|
endDate: dateNextMonth,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.getRawOne()
|
.groupBy('target_month')
|
||||||
|
.orderBy('target_month', 'ASC')
|
||||||
|
.getRawMany()
|
||||||
|
|
||||||
|
const map = new Map()
|
||||||
|
if (Array.isArray(createdAmountsQuery) && createdAmountsQuery.length > 0) {
|
||||||
|
createdAmountsQuery.forEach((createdAmount) => {
|
||||||
|
if (!map.has(createdAmount.target_month)) {
|
||||||
|
map.set(createdAmount.target_month, createdAmount.sum)
|
||||||
|
} else {
|
||||||
|
const store = map.get(createdAmount.target_month)
|
||||||
|
map.set(createdAmount.target_month, Number(store) + Number(createdAmount.sum))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(pendingAmountsQuery) && pendingAmountsQuery.length > 0) {
|
||||||
|
pendingAmountsQuery.forEach((pendingAmount) => {
|
||||||
|
if (!map.has(pendingAmount.target_month)) {
|
||||||
|
map.set(pendingAmount.target_month, pendingAmount.sum)
|
||||||
|
} else {
|
||||||
|
const store = map.get(pendingAmount.target_month)
|
||||||
|
map.set(pendingAmount.target_month, Number(store) + Number(pendingAmount.sum))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const usedCreationBeforeLastMonth = map.get(Number(beforeLastMonthNumber))
|
||||||
|
? Number(map.get(Number(beforeLastMonthNumber))) / 10000
|
||||||
|
: 0
|
||||||
|
const usedCreationLastMonth = map.get(Number(lastMonthNumber))
|
||||||
|
? Number(map.get(Number(lastMonthNumber))) / 10000
|
||||||
|
: 0
|
||||||
|
|
||||||
|
const usedCreationCurrentMonth = map.get(Number(currentMonthNumber))
|
||||||
|
? Number(map.get(Number(currentMonthNumber))) / 10000
|
||||||
|
: 0
|
||||||
|
|
||||||
// COUNT amount from 2 tables
|
|
||||||
const usedCreationBeforeLastMonth =
|
|
||||||
(Number(createdAmountBeforeLastMonth.sum) + Number(pendingAmountBeforeLastMounth.sum)) / 10000
|
|
||||||
const usedCreationLastMonth =
|
|
||||||
(Number(createdAmountLastMonth.sum) + Number(pendingAmountLastMounth.sum)) / 10000
|
|
||||||
const usedCreationMonth =
|
|
||||||
(Number(createdAmountMonth.sum) + Number(pendingAmountMounth.sum)) / 10000
|
|
||||||
return [
|
return [
|
||||||
1000 - usedCreationBeforeLastMonth,
|
1000 - usedCreationBeforeLastMonth,
|
||||||
1000 - usedCreationLastMonth,
|
1000 - usedCreationLastMonth,
|
||||||
1000 - usedCreationMonth,
|
1000 - usedCreationCurrentMonth,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -428,7 +428,7 @@ async function addUserTransaction(
|
|||||||
if (lastUserTransaction) {
|
if (lastUserTransaction) {
|
||||||
newBalance += Number(
|
newBalance += Number(
|
||||||
await calculateDecay(
|
await calculateDecay(
|
||||||
Number(lastUserTransaction.balance * 10000),
|
Number(lastUserTransaction.balance),
|
||||||
lastUserTransaction.balanceDate,
|
lastUserTransaction.balanceDate,
|
||||||
transaction.received,
|
transaction.received,
|
||||||
).catch(() => {
|
).catch(() => {
|
||||||
|
|||||||
@ -2,4 +2,4 @@ import { EntityRepository, Repository } from 'typeorm'
|
|||||||
import { LoginPendingTasksAdmin } from '@entity/LoginPendingTasksAdmin'
|
import { LoginPendingTasksAdmin } from '@entity/LoginPendingTasksAdmin'
|
||||||
|
|
||||||
@EntityRepository(LoginPendingTasksAdmin)
|
@EntityRepository(LoginPendingTasksAdmin)
|
||||||
export class PendingCreationRepository extends Repository<LoginPendingTasksAdmin> {}
|
export class LoginPendingTasksAdminRepository extends Repository<LoginPendingTasksAdmin> {}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
##################################################################################
|
##################################################################################
|
||||||
# BASE ###########################################################################
|
# BASE ###########################################################################
|
||||||
##################################################################################
|
##################################################################################
|
||||||
FROM node:12.19.0-alpine3.10 as base
|
FROM node:17-alpine as base
|
||||||
|
|
||||||
# ENVs (available in production aswell, can be overwritten by commandline or env file)
|
# ENVs (available in production aswell, can be overwritten by commandline or env file)
|
||||||
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
|
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
|
||||||
@ -14,8 +14,6 @@ ENV BUILD_VERSION="0.0.0.0"
|
|||||||
ENV BUILD_COMMIT="0000000"
|
ENV BUILD_COMMIT="0000000"
|
||||||
## SET NODE_ENV
|
## SET NODE_ENV
|
||||||
ENV NODE_ENV="production"
|
ENV NODE_ENV="production"
|
||||||
## App relevant Envs
|
|
||||||
#ENV PORT="4000"
|
|
||||||
|
|
||||||
# Labels
|
# Labels
|
||||||
LABEL org.label-schema.build-date="${BUILD_DATE}"
|
LABEL org.label-schema.build-date="${BUILD_DATE}"
|
||||||
@ -34,10 +32,6 @@ LABEL maintainer="support@gradido.net"
|
|||||||
## install: git
|
## install: git
|
||||||
#RUN apk --no-cache add git
|
#RUN apk --no-cache add git
|
||||||
|
|
||||||
# Settings
|
|
||||||
## Expose Container Port
|
|
||||||
# EXPOSE ${PORT}
|
|
||||||
|
|
||||||
## Workdir
|
## Workdir
|
||||||
RUN mkdir -p ${DOCKER_WORKDIR}
|
RUN mkdir -p ${DOCKER_WORKDIR}
|
||||||
WORKDIR ${DOCKER_WORKDIR}
|
WORKDIR ${DOCKER_WORKDIR}
|
||||||
@ -99,7 +93,7 @@ FROM base as production
|
|||||||
# Copy "binary"-files from build image
|
# Copy "binary"-files from build image
|
||||||
COPY --from=build ${DOCKER_WORKDIR}/build ./build
|
COPY --from=build ${DOCKER_WORKDIR}/build ./build
|
||||||
# We also copy the node_modules express and serve-static for the run script
|
# We also copy the node_modules express and serve-static for the run script
|
||||||
COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
|
COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
|
||||||
# Copy static files
|
# Copy static files
|
||||||
# COPY --from=build ${DOCKER_WORKDIR}/public ./public
|
# COPY --from=build ${DOCKER_WORKDIR}/public ./public
|
||||||
# Copy package.json for script definitions (lock file should not be needed)
|
# Copy package.json for script definitions (lock file should not be needed)
|
||||||
|
|||||||
@ -13,12 +13,12 @@
|
|||||||
"up": "cd build && node src/index.js up",
|
"up": "cd build && node src/index.js up",
|
||||||
"down": "cd build && node src/index.js down",
|
"down": "cd build && node src/index.js down",
|
||||||
"reset": "cd build && node src/index.js reset",
|
"reset": "cd build && node src/index.js reset",
|
||||||
"dev_up": "nodemon -w ./ --ext ts --exec ts-node src/index.ts up",
|
"dev_up": "ts-node src/index.ts up",
|
||||||
"dev_down": "nodemon -w ./ --ext ts --exec ts-node src/index.ts down",
|
"dev_down": "ts-node src/index.ts down",
|
||||||
"dev_reset": "nodemon -w ./ --ext ts --exec ts-node src/index.ts reset",
|
"dev_reset": "ts-node src/index.ts reset",
|
||||||
"lint": "eslint . --ext .js,.ts",
|
"lint": "eslint . --ext .js,.ts",
|
||||||
"seed:config": "ts-node ./node_modules/typeorm-seeding/dist/cli.js config",
|
"seed:config": "ts-node ./node_modules/typeorm-seeding/dist/cli.js config",
|
||||||
"seed": "nodemon -w ./ --ext ts --exec ts-node src/index.ts seed"
|
"seed": "ts-node src/index.ts seed"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/faker": "^5.5.9",
|
"@types/faker": "^5.5.9",
|
||||||
@ -32,7 +32,6 @@
|
|||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-prettier": "^3.4.1",
|
"eslint-plugin-prettier": "^3.4.1",
|
||||||
"eslint-plugin-promise": "^5.1.0",
|
"eslint-plugin-promise": "^5.1.0",
|
||||||
"nodemon": "^2.0.12",
|
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -68,9 +68,6 @@ services:
|
|||||||
image: gradido/database:test_up
|
image: gradido/database:test_up
|
||||||
build:
|
build:
|
||||||
target: test_up
|
target: test_up
|
||||||
#networks:
|
|
||||||
# - external-net
|
|
||||||
# - internal-net
|
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV="development"
|
- NODE_ENV="development"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@ -116,12 +116,10 @@ services:
|
|||||||
- mariadb
|
- mariadb
|
||||||
networks:
|
networks:
|
||||||
- internal-net
|
- internal-net
|
||||||
#ports:
|
- external-net # this is required to fetch the packages
|
||||||
# - 4000:4000
|
|
||||||
environment:
|
environment:
|
||||||
# Envs used in Dockerfile
|
# Envs used in Dockerfile
|
||||||
# - DOCKER_WORKDIR="/app"
|
# - DOCKER_WORKDIR="/app"
|
||||||
# - PORT=4000
|
|
||||||
- BUILD_DATE
|
- BUILD_DATE
|
||||||
- BUILD_VERSION
|
- BUILD_VERSION
|
||||||
- BUILD_COMMIT
|
- BUILD_COMMIT
|
||||||
|
|||||||
@ -1,12 +1,22 @@
|
|||||||
module.exports = {
|
module.exports = function (api) {
|
||||||
presets: ['@babel/preset-env'],
|
api.cache(true)
|
||||||
plugins: [
|
|
||||||
'transform-require-context',
|
const presets = ['@babel/preset-env']
|
||||||
|
const plugins = [
|
||||||
[
|
[
|
||||||
'component',
|
'component',
|
||||||
{
|
{
|
||||||
styleLibraryName: 'theme-chalk',
|
styleLibraryName: 'theme-chalk',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
]
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
plugins.push('transform-require-context')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
presets,
|
||||||
|
plugins,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,8 +23,6 @@
|
|||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"babel-jest": "^27.3.1",
|
"babel-jest": "^27.3.1",
|
||||||
"babel-plugin-require-context-hook": "^1.0.0",
|
|
||||||
"babel-plugin-transform-require-context": "^0.1.1",
|
|
||||||
"babel-preset-vue": "^2.0.2",
|
"babel-preset-vue": "^2.0.2",
|
||||||
"bootstrap": "4.3.1",
|
"bootstrap": "4.3.1",
|
||||||
"bootstrap-vue": "^2.5.0",
|
"bootstrap-vue": "^2.5.0",
|
||||||
@ -90,6 +88,7 @@
|
|||||||
"@vue/eslint-config-prettier": "^4.0.1",
|
"@vue/eslint-config-prettier": "^4.0.1",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-plugin-component": "^1.1.0",
|
"babel-plugin-component": "^1.1.0",
|
||||||
|
"babel-plugin-transform-require-context": "^0.1.1",
|
||||||
"dotenv-webpack": "^7.0.3",
|
"dotenv-webpack": "^7.0.3",
|
||||||
"node-sass": "^6.0.1",
|
"node-sass": "^6.0.1",
|
||||||
"sass-loader": "^10",
|
"sass-loader": "^10",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app" class="font-sans text-gray-800">
|
<div id="app" class="font-sans text-gray-800">
|
||||||
<div class="">
|
<div>
|
||||||
<particles-bg v-if="$store.state.coinanimation" type="custom" :config="config" :bg="true" />
|
<particles-bg v-if="$store.state.coinanimation" type="custom" :config="config" :bg="true" />
|
||||||
<component :is="$route.meta.requiresAuth ? 'DashboardLayout' : 'AuthLayoutGDD'" />
|
<component :is="$route.meta.requiresAuth ? 'DashboardLayout' : 'AuthLayoutGDD'" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
103
frontend/src/components/Menu/Navbar.spec.js
Normal file
103
frontend/src/components/Menu/Navbar.spec.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Navbar from './Navbar'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const propsData = {
|
||||||
|
balance: 1234,
|
||||||
|
visible: false,
|
||||||
|
elopageUri: 'https://elopage.com',
|
||||||
|
}
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$i18n: {
|
||||||
|
locale: 'en',
|
||||||
|
},
|
||||||
|
$t: jest.fn((t) => t),
|
||||||
|
$store: {
|
||||||
|
state: {
|
||||||
|
hasElopage: false,
|
||||||
|
isAdmin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Navbar', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(Navbar, { localVue, propsData, mocks })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component', () => {
|
||||||
|
expect(wrapper.find('div.component-navbar').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('navigation Navbar', () => {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has first nav-item "amount GDD" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('1234 GDD')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has first nav-item "overview" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('overview')
|
||||||
|
})
|
||||||
|
it('has first nav-item "send" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(4).text()).toEqual('send')
|
||||||
|
})
|
||||||
|
it('has first nav-item "transactions" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('transactions')
|
||||||
|
})
|
||||||
|
it('has first nav-item "my-profil" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('site.navbar.my-profil')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a link to the members area', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(7).text()).toContain('members_area')
|
||||||
|
expect(wrapper.findAll('.nav-item').at(7).find('a').attributes('href')).toBe(
|
||||||
|
'https://elopage.com',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has first nav-item "admin_area" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('admin_area')
|
||||||
|
})
|
||||||
|
it('has first nav-item "logout" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('logout')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('check watch visible true', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.setProps({ visible: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has visibleCollapse == visible', () => {
|
||||||
|
expect(wrapper.vm.visibleCollapse).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('check watch visible false', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.setProps({ visible: false })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has visibleCollapse == visible', () => {
|
||||||
|
expect(wrapper.vm.visibleCollapse).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
111
frontend/src/components/Menu/Navbar.vue
Normal file
111
frontend/src/components/Menu/Navbar.vue
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<div class="component-navbar" style="background-color: #fff">
|
||||||
|
<b-navbar toggleable="lg" type="light" variant="faded">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<b-navbar-nav @click="$emit('set-visible', false)">
|
||||||
|
<b-nav-item to="/overview">
|
||||||
|
<img :src="logo" class="navbar-brand-img" alt="..." />
|
||||||
|
</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<b-navbar-nav class="ml-auto" is-nav>
|
||||||
|
<b-nav-item>{{ balance }} GDD</b-nav-item>
|
||||||
|
<b-nav-item to="/profile" right class="d-none d-sm-none d-md-none d-lg-flex shadow-lg">
|
||||||
|
<small>
|
||||||
|
{{ $store.state.firstName }} {{ $store.state.lastName }},
|
||||||
|
<b>{{ $store.state.email }}</b>
|
||||||
|
<b-icon class="ml-3" icon="gear-fill" aria-hidden="true"></b-icon>
|
||||||
|
</small>
|
||||||
|
</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
|
||||||
|
<b-navbar-toggle
|
||||||
|
target="false"
|
||||||
|
@click="$emit('set-visible', (visibleCollapse = !visible))"
|
||||||
|
></b-navbar-toggle>
|
||||||
|
</b-navbar>
|
||||||
|
|
||||||
|
<b-collapse id="collapse-nav" v-model="visibleCollapse" class="p-3 b-collaps-gradido">
|
||||||
|
<b-nav vertical @click="$emit('set-visible', false)">
|
||||||
|
<div class="text-right">
|
||||||
|
<b-link to="/profile">
|
||||||
|
<small>
|
||||||
|
{{ $store.state.firstName }}
|
||||||
|
{{ $store.state.lastName }},
|
||||||
|
<b>{{ $store.state.email }}</b>
|
||||||
|
</small>
|
||||||
|
</b-link>
|
||||||
|
</div>
|
||||||
|
<b-nav-item to="/overview" class="mb-3">
|
||||||
|
{{ $t('overview') }}
|
||||||
|
</b-nav-item>
|
||||||
|
<b-nav-item to="/send" class="mb-3">{{ $t('send') }}</b-nav-item>
|
||||||
|
<b-nav-item to="/transactions" class="mb-3">
|
||||||
|
{{ $t('transactions') }}
|
||||||
|
</b-nav-item>
|
||||||
|
<b-nav-item to="/profile" class="mb-3">
|
||||||
|
<b-icon icon="gear-fill" aria-hidden="true"></b-icon>
|
||||||
|
{{ $t('site.navbar.my-profil') }}
|
||||||
|
</b-nav-item>
|
||||||
|
<br />
|
||||||
|
<b-nav-item :href="elopageUri" class="mb-3" target="_blank">
|
||||||
|
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
|
||||||
|
{{ $t('members_area') }}
|
||||||
|
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">!</b-badge>
|
||||||
|
</b-nav-item>
|
||||||
|
<b-nav-item class="mb-3" v-if="$store.state.isAdmin" @click="$emit('admin')">
|
||||||
|
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
|
||||||
|
{{ $t('admin_area') }}
|
||||||
|
</b-nav-item>
|
||||||
|
<b-nav-item class="mb-3" @click="$emit('logout')">
|
||||||
|
<b-icon icon="power" aria-hidden="true"></b-icon>
|
||||||
|
{{ $t('logout') }}
|
||||||
|
</b-nav-item>
|
||||||
|
</b-nav>
|
||||||
|
</b-collapse>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'navbar',
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
elopageUri: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
logo: 'img/brand/green.png',
|
||||||
|
visibleCollapse: this.visible,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible() {
|
||||||
|
this.visibleCollapse = this.visible
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.b-collaps-gradido {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100000;
|
||||||
|
background-color: #dfe0e3f5;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: #b4b4b4 0px 13px 22px;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
.b-collaps-gradido li :hover {
|
||||||
|
background-color: #e9e7e7f5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
65
frontend/src/components/Menu/Sidebar.spec.js
Normal file
65
frontend/src/components/Menu/Sidebar.spec.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Sidebar from './Sidebar.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
describe('Sidebar', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$i18n: {
|
||||||
|
locale: 'en',
|
||||||
|
},
|
||||||
|
$t: jest.fn((t) => t),
|
||||||
|
$store: {
|
||||||
|
state: {
|
||||||
|
hasElopage: true,
|
||||||
|
isAdmin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(Sidebar, { localVue, mocks })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
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(7)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has first nav-item "overview" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(0).text()).toEqual('overview')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has first nav-item "send" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('send')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has first nav-item "transactions" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(2).text()).toEqual('transactions')
|
||||||
|
})
|
||||||
|
it('has first nav-item "my-profil" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('site.navbar.my-profil')
|
||||||
|
})
|
||||||
|
it('has a link to the members area', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(4).text()).toContain('members_area')
|
||||||
|
expect(wrapper.findAll('.nav-item').at(4).find('a').attributes('href')).toBe('#')
|
||||||
|
})
|
||||||
|
it('has first nav-item "admin_area" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('admin_area')
|
||||||
|
})
|
||||||
|
it('has first nav-item "logout" in navbar', () => {
|
||||||
|
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('logout')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
45
frontend/src/components/Menu/Sidebar.vue
Normal file
45
frontend/src/components/Menu/Sidebar.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div id="component-sidebar">
|
||||||
|
<div class="pl-3">
|
||||||
|
<p></p>
|
||||||
|
<div class="mb-6">
|
||||||
|
<b-nav vertical class="w-200">
|
||||||
|
<b-nav-item to="/overview" class="mb-3" active>{{ $t('overview') }}</b-nav-item>
|
||||||
|
<b-nav-item to="/send" class="mb-3">{{ $t('send') }}</b-nav-item>
|
||||||
|
<b-nav-item to="/transactions" class="mb-3">{{ $t('transactions') }}</b-nav-item>
|
||||||
|
<b-nav-item to="/profile" class="mb-3">
|
||||||
|
<b-icon icon="gear-fill" aria-hidden="true"></b-icon>
|
||||||
|
{{ $t('site.navbar.my-profil') }}
|
||||||
|
</b-nav-item>
|
||||||
|
</b-nav>
|
||||||
|
<hr />
|
||||||
|
<b-nav vertical class="w-100">
|
||||||
|
<b-nav-item class="mb-3" :href="elopageUri" target="_blank">
|
||||||
|
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
|
||||||
|
{{ $t('members_area') }}
|
||||||
|
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">!</b-badge>
|
||||||
|
</b-nav-item>
|
||||||
|
<b-nav-item class="mb-3" v-if="$store.state.isAdmin" @click="$emit('admin')">
|
||||||
|
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
|
||||||
|
{{ $t('admin_area') }}
|
||||||
|
</b-nav-item>
|
||||||
|
<b-nav-item class="mb-3" @click="$emit('logout')">
|
||||||
|
<b-icon icon="power" aria-hidden="true"></b-icon>
|
||||||
|
{{ $t('logout') }}
|
||||||
|
</b-nav-item>
|
||||||
|
</b-nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'sidebar',
|
||||||
|
props: {
|
||||||
|
elopageUri: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,21 +0,0 @@
|
|||||||
<template>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="navbar-toggler collapsed"
|
|
||||||
data-toggle="collapse"
|
|
||||||
data-target="#navbar"
|
|
||||||
aria-controls="navbarSupportedContent"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-label="Toggle navigation"
|
|
||||||
>
|
|
||||||
<span class="navbar-toggler-bar bar1"></span>
|
|
||||||
<span class="navbar-toggler-bar bar2"></span>
|
|
||||||
<span class="navbar-toggler-bar bar3"></span>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'navbar-toggle-button',
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style></style>
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
<template>
|
|
||||||
<button
|
|
||||||
class="navbar-toggler"
|
|
||||||
type="button"
|
|
||||||
data-toggle="collapse"
|
|
||||||
:data-target="target"
|
|
||||||
:aria-controls="target"
|
|
||||||
:aria-expanded="toggled"
|
|
||||||
aria-label="Toggle navigation"
|
|
||||||
>
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
<slot>
|
|
||||||
<span></span>
|
|
||||||
</slot>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
target: {
|
|
||||||
type: [String, Number],
|
|
||||||
description: 'Button target element',
|
|
||||||
},
|
|
||||||
toggled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
description: 'Whether button is toggled',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style></style>
|
|
||||||
@ -1,200 +0,0 @@
|
|||||||
import { mount, RouterLinkStub } from '@vue/test-utils'
|
|
||||||
import SideBar from './SideBar'
|
|
||||||
|
|
||||||
const localVue = global.localVue
|
|
||||||
|
|
||||||
const storeDispatchMock = jest.fn()
|
|
||||||
|
|
||||||
describe('SideBar', () => {
|
|
||||||
let wrapper
|
|
||||||
|
|
||||||
const stubs = {
|
|
||||||
RouterLink: RouterLinkStub,
|
|
||||||
}
|
|
||||||
|
|
||||||
const propsData = {
|
|
||||||
balance: 1234.56,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
$store: {
|
|
||||||
state: {
|
|
||||||
email: 'test@example.org',
|
|
||||||
publisherId: 123,
|
|
||||||
firstName: 'test',
|
|
||||||
lastName: 'example',
|
|
||||||
hasElopage: false,
|
|
||||||
},
|
|
||||||
dispatch: storeDispatchMock,
|
|
||||||
},
|
|
||||||
$i18n: {
|
|
||||||
locale: 'en',
|
|
||||||
},
|
|
||||||
$t: jest.fn((t) => t),
|
|
||||||
$n: jest.fn((n) => n),
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return mount(SideBar, { localVue, mocks, stubs, propsData })
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('mount', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders the component', () => {
|
|
||||||
expect(wrapper.find('#sidenav-main').exists()).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('navbar button', () => {
|
|
||||||
it('has a navbar button', () => {
|
|
||||||
expect(wrapper.find('button.navbar-toggler').exists()).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls showSidebar when clicked', async () => {
|
|
||||||
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
|
|
||||||
wrapper.find('button.navbar-toggler').trigger('click')
|
|
||||||
await wrapper.vm.$nextTick()
|
|
||||||
expect(spy).toHaveBeenCalledWith(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('balance', () => {
|
|
||||||
it('shows em-dash as balance while loading', () => {
|
|
||||||
expect(wrapper.find('div.row.text-center').text()).toBe('— GDD')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows the when loaded', async () => {
|
|
||||||
wrapper.setProps({
|
|
||||||
pending: false,
|
|
||||||
})
|
|
||||||
await wrapper.vm.$nextTick()
|
|
||||||
expect(wrapper.find('div.row.text-center').text()).toBe('1234.56 GDD')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('close siedbar', () => {
|
|
||||||
it('calls closeSidebar when clicked', async () => {
|
|
||||||
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
|
|
||||||
wrapper.find('#sidenav-collapse-main').find('button.navbar-toggler').trigger('click')
|
|
||||||
await wrapper.vm.$nextTick()
|
|
||||||
expect(spy).toHaveBeenCalledWith(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('static menu items', () => {
|
|
||||||
describe("member's area without publisher ID", () => {
|
|
||||||
it('has a link to the elopage', () => {
|
|
||||||
expect(wrapper.findAll('li').at(0).text()).toContain('members_area')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a badge', () => {
|
|
||||||
expect(wrapper.findAll('li').at(0).text()).toContain('!')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('links to the elopage registration', () => {
|
|
||||||
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
|
|
||||||
'https://elopage.com/s/gradido/basic-de/payment?locale=en&prid=111&pid=123&firstName=test&lastName=example&email=test@example.org',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with locale="de"', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
mocks.$i18n.locale = 'de'
|
|
||||||
})
|
|
||||||
|
|
||||||
it('links to the German elopage registration when locale is set to de', () => {
|
|
||||||
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
|
|
||||||
'https://elopage.com/s/gradido/basic-de/payment?locale=de&prid=111&pid=123&firstName=test&lastName=example&email=test@example.org',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("member's area with publisher ID", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
mocks.$store.state.hasElopage = true
|
|
||||||
})
|
|
||||||
|
|
||||||
it('links to the elopage member area', () => {
|
|
||||||
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
|
|
||||||
'https://elopage.com/s/gradido/sign_in?locale=de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has no badge', () => {
|
|
||||||
expect(wrapper.findAll('li').at(0).text()).not.toContain('!')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("member's area with default publisher ID and no elopage", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
mocks.$store.state.publisherId = null
|
|
||||||
mocks.$store.state.hasElopage = false
|
|
||||||
})
|
|
||||||
|
|
||||||
it('links to the elopage member area with default publisher ID', () => {
|
|
||||||
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
|
|
||||||
'https://elopage.com/s/gradido/basic-de/payment?locale=de&prid=111&pid=2896&firstName=test&lastName=example&email=test@example.org',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a badge', () => {
|
|
||||||
expect(wrapper.findAll('li').at(0).text()).toContain('!')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('logout', () => {
|
|
||||||
it('has a logout button', () => {
|
|
||||||
expect(wrapper.findAll('li').at(1).text()).toBe('logout')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('emits logout when logout is clicked', async () => {
|
|
||||||
wrapper.findAll('li').at(1).find('a').trigger('click')
|
|
||||||
await wrapper.vm.$nextTick()
|
|
||||||
expect(wrapper.emitted('logout')).toEqual([[]])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('admin-area', () => {
|
|
||||||
it('is not visible when not an admin', () => {
|
|
||||||
expect(wrapper.findAll('li').at(1).text()).not.toBe('admin_area')
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('logged in as admin', () => {
|
|
||||||
const assignLocationSpy = jest.fn()
|
|
||||||
beforeEach(async () => {
|
|
||||||
mocks.$store.state.isAdmin = true
|
|
||||||
mocks.$store.state.token = 'valid-token'
|
|
||||||
delete window.location
|
|
||||||
window.location = {
|
|
||||||
assign: assignLocationSpy,
|
|
||||||
}
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('is visible', () => {
|
|
||||||
expect(wrapper.findAll('li').at(1).text()).toBe('admin_area')
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('click on admin area', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await wrapper.findAll('li').at(1).find('a').trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('opens a new window when clicked', () => {
|
|
||||||
expect(assignLocationSpy).toHaveBeenCalledWith(
|
|
||||||
'http://localhost/admin/authenticate?token=valid-token',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('dispatches logout to store', () => {
|
|
||||||
expect(storeDispatchMock).toHaveBeenCalledWith('logout')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
<template>
|
|
||||||
<nav
|
|
||||||
class="navbar navbar-vertical fixed-left navbar-expand-md navbar-light bg-transparent"
|
|
||||||
id="sidenav-main"
|
|
||||||
>
|
|
||||||
<div class="container-fluid">
|
|
||||||
<!--Toggler-->
|
|
||||||
<navbar-toggle-button @click.native="showSidebar"></navbar-toggle-button>
|
|
||||||
<div class="navbar-brand">
|
|
||||||
<img :src="logo" class="navbar-brand-img" alt="..." />
|
|
||||||
</div>
|
|
||||||
<b-row class="text-center">
|
|
||||||
<b-col>{{ pending ? '—' : $n(balance, 'decimal') }} GDD</b-col>
|
|
||||||
</b-row>
|
|
||||||
<slot name="mobile-right">
|
|
||||||
<ul class="nav align-items-center d-md-none">
|
|
||||||
<div class="media align-items-center">
|
|
||||||
<span class="avatar avatar-sm">
|
|
||||||
<vue-qrcode
|
|
||||||
v-if="$store.state.email"
|
|
||||||
:value="$store.state.email"
|
|
||||||
type="image/png"
|
|
||||||
></vue-qrcode>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</ul>
|
|
||||||
</slot>
|
|
||||||
<slot></slot>
|
|
||||||
<div
|
|
||||||
v-show="$sidebar.showSidebar"
|
|
||||||
class="navbar-collapse collapse show"
|
|
||||||
id="sidenav-collapse-main"
|
|
||||||
>
|
|
||||||
<div class="navbar-collapse-header d-md-none">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-6 collapse-brand">
|
|
||||||
<img :src="logo" />
|
|
||||||
</div>
|
|
||||||
<div class="col-6 collapse-close">
|
|
||||||
<navbar-toggle-button @click.native="closeSidebar"></navbar-toggle-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="navbar-nav">
|
|
||||||
<slot name="links"></slot>
|
|
||||||
</ul>
|
|
||||||
<hr class="my-2" />
|
|
||||||
|
|
||||||
<ul class="navbar-nav ml-3">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a :href="getElopageLink()" class="nav-link" target="_blank">
|
|
||||||
{{ $t('members_area') }}
|
|
||||||
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">!</b-badge>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="navbar-nav ml-3" v-if="$store.state.isAdmin">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link pointer" @click="admin">
|
|
||||||
{{ $t('admin_area') }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="navbar-nav ml-3">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link pointer" @click="logout">
|
|
||||||
{{ $t('logout') }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import NavbarToggleButton from '@/components/NavbarToggleButton'
|
|
||||||
import VueQrcode from 'vue-qrcode'
|
|
||||||
import CONFIG from '../../config'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'sidebar',
|
|
||||||
components: {
|
|
||||||
NavbarToggleButton,
|
|
||||||
VueQrcode,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
logo: {
|
|
||||||
type: String,
|
|
||||||
default: 'img/brand/green.png',
|
|
||||||
description: 'Gradido Sidebar app logo',
|
|
||||||
},
|
|
||||||
value: { type: String },
|
|
||||||
autoClose: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
description: 'Whether sidebar should autoclose on mobile when clicking an item',
|
|
||||||
},
|
|
||||||
balance: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
pending: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
autoClose: this.autoClose,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
closeSidebar() {
|
|
||||||
this.$sidebar.displaySidebar(false)
|
|
||||||
},
|
|
||||||
showSidebar() {
|
|
||||||
this.$sidebar.displaySidebar(true)
|
|
||||||
},
|
|
||||||
logout() {
|
|
||||||
this.$emit('logout')
|
|
||||||
},
|
|
||||||
admin() {
|
|
||||||
window.location.assign(CONFIG.ADMIN_AUTH_URL.replace('$1', this.$store.state.token))
|
|
||||||
this.$store.dispatch('logout') // logout without redirect
|
|
||||||
},
|
|
||||||
getElopageLink() {
|
|
||||||
const pId = this.$store.state.publisherId
|
|
||||||
? this.$store.state.publisherId
|
|
||||||
: CONFIG.DEFAULT_PUBLISHER_ID
|
|
||||||
return encodeURI(
|
|
||||||
this.$store.state.hasElopage
|
|
||||||
? `https://elopage.com/s/gradido/sign_in?locale=${this.$i18n.locale}`
|
|
||||||
: `https://elopage.com/s/gradido/basic-de/payment?locale=${this.$i18n.locale}&prid=111&pid=${pId}&firstName=${this.$store.state.firstName}&lastName=${this.$store.state.lastName}&email=${this.$store.state.email}`,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,189 +0,0 @@
|
|||||||
<template>
|
|
||||||
<b-nav-item
|
|
||||||
:is="baseComponent"
|
|
||||||
:to="link.path ? link.path : '/'"
|
|
||||||
class="nav-item"
|
|
||||||
:class="{ active: isActive }"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
v-if="isMenu"
|
|
||||||
class="sidebar-menu-item nav-link"
|
|
||||||
:class="{ active: isActive }"
|
|
||||||
:aria-expanded="!collapsed"
|
|
||||||
data-toggle="collapse"
|
|
||||||
@click.prevent="collapseMenu"
|
|
||||||
>
|
|
||||||
<template v-if="addLink">
|
|
||||||
<span class="nav-link-text">
|
|
||||||
{{ link.name }}
|
|
||||||
<b class="caret"></b>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<i :class="link.icon"></i>
|
|
||||||
<span class="nav-link-text">
|
|
||||||
{{ link.name }}
|
|
||||||
<b class="caret"></b>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<collapse-transition>
|
|
||||||
<div v-if="$slots.default || this.isMenu" v-show="!collapsed" class="collapse show">
|
|
||||||
<ul class="nav nav-sm flex-column">
|
|
||||||
<slot></slot>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</collapse-transition>
|
|
||||||
|
|
||||||
<slot name="title" v-if="children.length === 0 && !$slots.default && link.path">
|
|
||||||
<component
|
|
||||||
:to="link.path"
|
|
||||||
@click.native="linkClick"
|
|
||||||
:is="elementType(link, false)"
|
|
||||||
class="nav-link"
|
|
||||||
:class="{ active: link.active }"
|
|
||||||
:target="link.target"
|
|
||||||
:href="link.path"
|
|
||||||
>
|
|
||||||
<template v-if="addLink">
|
|
||||||
<span class="nav-link-text">{{ link.name }}</span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<i :class="link.icon"></i>
|
|
||||||
<span class="nav-link-text">{{ link.name }}</span>
|
|
||||||
</template>
|
|
||||||
</component>
|
|
||||||
</slot>
|
|
||||||
</b-nav-item>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { CollapseTransition } from 'vue2-transitions'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'sidebar-item',
|
|
||||||
components: {
|
|
||||||
CollapseTransition,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
menu: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
description:
|
|
||||||
"Whether the item is a menu. Most of the item it's not used and should be used only if you want to override the default behavior.",
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {
|
|
||||||
return {
|
|
||||||
name: '',
|
|
||||||
path: '',
|
|
||||||
children: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
description:
|
|
||||||
'Sidebar link. Can contain name, path, icon and other attributes. See examples for more info',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
addLink: this.addChild,
|
|
||||||
removeLink: this.removeChild,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inject: {
|
|
||||||
addLink: { default: null },
|
|
||||||
removeLink: { default: null },
|
|
||||||
autoClose: {
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
children: [],
|
|
||||||
collapsed: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
baseComponent() {
|
|
||||||
return this.isMenu || this.link.isRoute ? 'li' : 'router-link'
|
|
||||||
},
|
|
||||||
linkPrefix() {
|
|
||||||
if (this.link.name) {
|
|
||||||
const words = this.link.name.split(' ')
|
|
||||||
return words.map((word) => word.substring(0, 1)).join('')
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
},
|
|
||||||
isMenu() {
|
|
||||||
return this.children.length > 0 || this.menu === true
|
|
||||||
},
|
|
||||||
isActive() {
|
|
||||||
if (this.$route && this.$route.path) {
|
|
||||||
const matchingRoute = this.children.find((c) => this.$route.path.startsWith(c.link.path))
|
|
||||||
if (matchingRoute !== undefined) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addChild(item) {
|
|
||||||
const index = this.$slots.default.indexOf(item.$vnode)
|
|
||||||
this.children.splice(index, 0, item)
|
|
||||||
},
|
|
||||||
removeChild(item) {
|
|
||||||
const tabs = this.children
|
|
||||||
const index = tabs.indexOf(item)
|
|
||||||
tabs.splice(index, 1)
|
|
||||||
},
|
|
||||||
elementType(link, isParent = true) {
|
|
||||||
if (link.isRoute === false) {
|
|
||||||
return isParent ? 'li' : 'a'
|
|
||||||
} else {
|
|
||||||
return 'router-link'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
linkAbbreviation(name) {
|
|
||||||
const matches = name.match(/\b(\w)/g)
|
|
||||||
return matches.join('')
|
|
||||||
},
|
|
||||||
linkClick() {
|
|
||||||
if (this.autoClose && this.$sidebar && this.$sidebar.showSidebar === true) {
|
|
||||||
this.$sidebar.displaySidebar(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
collapseMenu() {
|
|
||||||
this.collapsed = !this.collapsed
|
|
||||||
},
|
|
||||||
collapseSubMenu(link) {
|
|
||||||
link.collapsed = !link.collapsed
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (this.addLink) {
|
|
||||||
this.addLink(this)
|
|
||||||
}
|
|
||||||
if (this.link.collapsed !== undefined) {
|
|
||||||
this.collapsed = this.link.collapsed
|
|
||||||
}
|
|
||||||
if (this.isActive && this.isMenu) {
|
|
||||||
this.collapsed = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
if (this.$el && this.$el.parentNode) {
|
|
||||||
this.$el.parentNode.removeChild(this.$el)
|
|
||||||
}
|
|
||||||
if (this.removeLink) {
|
|
||||||
this.removeLink(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.sidebar-menu-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
import Sidebar from './SideBar.vue'
|
|
||||||
import SidebarItem from './SidebarItem.vue'
|
|
||||||
|
|
||||||
const SidebarStore = {
|
|
||||||
showSidebar: false,
|
|
||||||
sidebarLinks: [],
|
|
||||||
isMinimized: false,
|
|
||||||
displaySidebar(value) {
|
|
||||||
this.showSidebar = value
|
|
||||||
},
|
|
||||||
toggleMinimize() {
|
|
||||||
document.body.classList.toggle('sidebar-mini')
|
|
||||||
const simulateWindowResize = setInterval(() => {
|
|
||||||
window.dispatchEvent(new Event('resize'))
|
|
||||||
}, 180)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
clearInterval(simulateWindowResize)
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
this.isMinimized = !this.isMinimized
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const SidebarPlugin = {
|
|
||||||
install(Vue, options) {
|
|
||||||
if (options && options.sidebarLinks) {
|
|
||||||
SidebarStore.sidebarLinks = options.sidebarLinks
|
|
||||||
}
|
|
||||||
const app = new Vue({
|
|
||||||
data: {
|
|
||||||
sidebarStore: SidebarStore,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
Vue.prototype.$sidebar = app.sidebarStore
|
|
||||||
Vue.component('side-bar', Sidebar)
|
|
||||||
Vue.component('sidebar-item', SidebarItem)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SidebarPlugin
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import NavbarToggleButton from './Navbar/NavbarToggleButton'
|
|
||||||
|
|
||||||
import SidebarPlugin from './SidebarPlugin'
|
|
||||||
|
|
||||||
export { SidebarPlugin, NavbarToggleButton }
|
|
||||||
@ -45,9 +45,11 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"change-password": "Fehler beim Ändern des Passworts",
|
"change-password": "Fehler beim Ändern des Passworts",
|
||||||
|
"empty-transactionlist": "Es gab einen Fehler mit der Übermittlung der Anzahl deiner Transaktionen.",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
|
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
|
||||||
"no-email-verify": "Die Email wurde noch nicht bestätigt, bitte überprüfe deine Emails und klicke auf den Aktivierungslink!",
|
"no-email-verify": "Die Email wurde noch nicht bestätigt, bitte überprüfe deine Emails und klicke auf den Aktivierungslink!",
|
||||||
|
"no-transactionlist": "Es gab leider einen Fehler. Es wurden keine Transaktionen vom Server übermittelt",
|
||||||
"session-expired": "Die Sitzung wurde aus Sicherheitsgründen beendet."
|
"session-expired": "Die Sitzung wurde aus Sicherheitsgründen beendet."
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
@ -55,6 +57,7 @@
|
|||||||
"at": "am",
|
"at": "am",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"close": "schließen",
|
"close": "schließen",
|
||||||
|
"current_balance": "aktueller Kontostand",
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
"edit": "bearbeiten",
|
"edit": "bearbeiten",
|
||||||
@ -64,6 +67,7 @@
|
|||||||
"lastname": "Nachname",
|
"lastname": "Nachname",
|
||||||
"memo": "Nachricht",
|
"memo": "Nachricht",
|
||||||
"message": "Nachricht",
|
"message": "Nachricht",
|
||||||
|
"new_balance": "neuer Kontostand nach Bestätigung",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"passwordRepeat": "Passwort wiederholen",
|
"passwordRepeat": "Passwort wiederholen",
|
||||||
"password_new": "neues Passwort",
|
"password_new": "neues Passwort",
|
||||||
@ -88,7 +92,8 @@
|
|||||||
"is-not": "Du kannst dir selbst keine Gradidos überweisen",
|
"is-not": "Du kannst dir selbst keine Gradidos überweisen",
|
||||||
"usernmae-regex": "Der Username muss mit einem Buchstaben beginnen auf den mindestens zwei alfanumerische Zeichen folgen müssen.",
|
"usernmae-regex": "Der Username muss mit einem Buchstaben beginnen auf den mindestens zwei alfanumerische Zeichen folgen müssen.",
|
||||||
"usernmae-unique": "Der Username ist bereits vergeben."
|
"usernmae-unique": "Der Username ist bereits vergeben."
|
||||||
}
|
},
|
||||||
|
"your_amount": "Dein Betrag"
|
||||||
},
|
},
|
||||||
"gdt": {
|
"gdt": {
|
||||||
"action": "Aktion",
|
"action": "Aktion",
|
||||||
|
|||||||
@ -45,9 +45,11 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"change-password": "Error while changing password",
|
"change-password": "Error while changing password",
|
||||||
|
"empty-transactionlist": "There was an error with the transmission of the number of your transactions.",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"no-account": "Unfortunately we could not find an account to the given data!",
|
"no-account": "Unfortunately we could not find an account to the given data!",
|
||||||
"no-email-verify": "Your email is not activated yet, please check your emails and click the activation link!",
|
"no-email-verify": "Your email is not activated yet, please check your emails and click the activation link!",
|
||||||
|
"no-transactionlist": "Unfortunately, there was an error. No transactions have been sent from the server.",
|
||||||
"session-expired": "The session was closed for security reasons."
|
"session-expired": "The session was closed for security reasons."
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
@ -55,6 +57,7 @@
|
|||||||
"at": "at",
|
"at": "at",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
|
"current_balance": "current balance",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
@ -64,6 +67,7 @@
|
|||||||
"lastname": "Lastname",
|
"lastname": "Lastname",
|
||||||
"memo": "Message",
|
"memo": "Message",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
|
"new_balance": "account balance after confirmation",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"passwordRepeat": "Repeat password",
|
"passwordRepeat": "Repeat password",
|
||||||
"password_new": "New password",
|
"password_new": "New password",
|
||||||
@ -88,7 +92,8 @@
|
|||||||
"is-not": "You cannot send Gradidos to yourself",
|
"is-not": "You cannot send Gradidos to yourself",
|
||||||
"usernmae-regex": "The username must start with a letter, followed by at least two alphanumeric characters.",
|
"usernmae-regex": "The username must start with a letter, followed by at least two alphanumeric characters.",
|
||||||
"usernmae-unique": "The username is already taken."
|
"usernmae-unique": "The username is already taken."
|
||||||
}
|
},
|
||||||
|
"your_amount": "Your amount"
|
||||||
},
|
},
|
||||||
"gdt": {
|
"gdt": {
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import GlobalComponents from './globalComponents'
|
import GlobalComponents from './globalComponents'
|
||||||
import GlobalDirectives from './globalDirectives'
|
import GlobalDirectives from './globalDirectives'
|
||||||
import SideBar from '@/components/SidebarPlugin'
|
|
||||||
|
|
||||||
import Toasted from 'vue-toasted'
|
import Toasted from 'vue-toasted'
|
||||||
|
|
||||||
@ -28,7 +27,6 @@ export default {
|
|||||||
install(Vue) {
|
install(Vue) {
|
||||||
Vue.use(GlobalComponents)
|
Vue.use(GlobalComponents)
|
||||||
Vue.use(GlobalDirectives)
|
Vue.use(GlobalDirectives)
|
||||||
Vue.use(SideBar)
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
Vue.use(IconsPlugin)
|
Vue.use(IconsPlugin)
|
||||||
Vue.use(VueMoment)
|
Vue.use(VueMoment)
|
||||||
|
|||||||
@ -27,7 +27,7 @@ describe('dashboard plugin', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('vue toasted', () => {
|
describe('vue toasted', () => {
|
||||||
const toastedAction = vueUseMock.mock.calls[11][1].action.onClick
|
const toastedAction = vueUseMock.mock.calls[10][1].action.onClick
|
||||||
const goAwayMock = jest.fn()
|
const goAwayMock = jest.fn()
|
||||||
const toastObject = {
|
const toastObject = {
|
||||||
goAway: goAwayMock,
|
goAway: goAwayMock,
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// import clickOutside from '@/directives/click-ouside.js'
|
|
||||||
import { focus } from 'vue-focus'
|
import { focus } from 'vue-focus'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -7,7 +6,6 @@ import { focus } from 'vue-focus'
|
|||||||
|
|
||||||
const GlobalDirectives = {
|
const GlobalDirectives = {
|
||||||
install(Vue) {
|
install(Vue) {
|
||||||
// Vue.directive('click-outside', clickOutside)
|
|
||||||
Vue.directive('focus', focus)
|
Vue.directive('focus', focus)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
21
frontend/src/plugins/globalDirectives.test.js
Normal file
21
frontend/src/plugins/globalDirectives.test.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import GlobalDirectives from './globalDirectives'
|
||||||
|
import { focus } from 'vue-focus'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
jest.mock('vue-focus', () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
focus: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
jest.mock('vue')
|
||||||
|
|
||||||
|
const vueDirectiveMock = jest.fn()
|
||||||
|
Vue.directive = vueDirectiveMock
|
||||||
|
|
||||||
|
describe('globalDirectives', () => {
|
||||||
|
it('installs the focus directive', () => {
|
||||||
|
GlobalDirectives.install(Vue)
|
||||||
|
expect(vueDirectiveMock).toBeCalledWith('focus', focus)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -52,6 +52,7 @@ describe('DashboardLayoutGdd', () => {
|
|||||||
publisherId: 123,
|
publisherId: 123,
|
||||||
firstName: 'User',
|
firstName: 'User',
|
||||||
lastName: 'Example',
|
lastName: 'Example',
|
||||||
|
token: 'valid-token',
|
||||||
},
|
},
|
||||||
dispatch: storeDispatchMock,
|
dispatch: storeDispatchMock,
|
||||||
commit: storeCommitMock,
|
commit: storeCommitMock,
|
||||||
@ -72,8 +73,12 @@ describe('DashboardLayoutGdd', () => {
|
|||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has a navbar', () => {
|
||||||
|
expect(wrapper.find('.main-navbar').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it('has a sidebar', () => {
|
it('has a sidebar', () => {
|
||||||
expect(wrapper.find('nav#sidenav-main').exists()).toBeTruthy()
|
expect(wrapper.find('.main-sidebar').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a main content div', () => {
|
it('has a main content div', () => {
|
||||||
@ -81,64 +86,10 @@ describe('DashboardLayoutGdd', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('has a footer inside the main content', () => {
|
it('has a footer inside the main content', () => {
|
||||||
expect(wrapper.find('div.main-content').find('footer.footer').exists()).toBeTruthy()
|
expect(wrapper.find('div.main-page').find('footer.footer').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('navigation bar', () => {
|
describe('navigation bar', () => {
|
||||||
let navbar
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
navbar = wrapper.findAll('ul.navbar-nav').at(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has four items in the navbar', () => {
|
|
||||||
expect(navbar.findAll('ul > a')).toHaveLength(4)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has first item "overview" in navbar', () => {
|
|
||||||
expect(navbar.findAll('ul > a').at(0).text()).toEqual('overview')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has first item "overview" linked to overview in navbar', () => {
|
|
||||||
expect(navbar.findAll('ul > a > a').at(0).attributes('href')).toBe('/overview')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has second item "send" in navbar', () => {
|
|
||||||
expect(navbar.findAll('ul > a').at(1).text()).toEqual('send')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has second item "send" linked to /send in navbar', () => {
|
|
||||||
expect(wrapper.findAll('ul > a > a').at(1).attributes('href')).toBe('/send')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has third item "transactions" in navbar', () => {
|
|
||||||
expect(navbar.findAll('ul > a').at(2).text()).toEqual('transactions')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has third item "transactions" linked to transactions in navbar', async () => {
|
|
||||||
expect(wrapper.findAll('ul > a > a').at(2).attributes('href')).toBe('/transactions')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has fourth item "My profile" in navbar', () => {
|
|
||||||
expect(navbar.findAll('ul > a').at(3).text()).toEqual('site.navbar.my-profil')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has fourth item "My profile" linked to profile in navbar', async () => {
|
|
||||||
expect(wrapper.findAll('ul > a > a').at(3).attributes('href')).toBe('/profile')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a link to the members area', () => {
|
|
||||||
expect(wrapper.findAll('ul').at(2).text()).toContain('members_area')
|
|
||||||
expect(wrapper.findAll('ul').at(2).text()).toContain('!')
|
|
||||||
expect(wrapper.findAll('ul').at(2).find('a').attributes('href')).toBe(
|
|
||||||
'https://elopage.com/s/gradido/basic-de/payment?locale=en&prid=111&pid=123&firstName=User&lastName=Example&email=user@example.org',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a logout button', () => {
|
|
||||||
expect(wrapper.findAll('ul').at(3).text()).toBe('logout')
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('logout', () => {
|
describe('logout', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await apolloMock.mockResolvedValue({
|
await apolloMock.mockResolvedValue({
|
||||||
@ -272,6 +223,65 @@ describe('DashboardLayoutGdd', () => {
|
|||||||
expect(toasterMock).toBeCalledWith('Ouch!')
|
expect(toasterMock).toBeCalledWith('Ouch!')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('set visible method', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper.findComponent({ name: 'Navbar' }).vm.$emit('set-visible', true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets visible to true', () => {
|
||||||
|
expect(wrapper.vm.visible).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('elopage URI', () => {
|
||||||
|
describe('user has no publisher ID and no elopage', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$store.state.publisherId = null
|
||||||
|
mocks.$store.state.hasElopage = false
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('links to basic-de', () => {
|
||||||
|
expect(wrapper.vm.elopageUri).toBe(
|
||||||
|
'https://elopage.com/s/gradido/basic-de/payment?locale=en&prid=111&pid=2896&firstName=User&lastName=Example&email=user@example.org',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user has elopage', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$store.state.publisherId = '123'
|
||||||
|
mocks.$store.state.hasElopage = true
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('links to sign in for elopage', () => {
|
||||||
|
expect(wrapper.vm.elopageUri).toBe('https://elopage.com/s/gradido/sign_in?locale=en')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('admin method', () => {
|
||||||
|
const windowLocationMock = jest.fn()
|
||||||
|
beforeEach(() => {
|
||||||
|
delete window.location
|
||||||
|
window.location = {
|
||||||
|
assign: windowLocationMock,
|
||||||
|
}
|
||||||
|
wrapper.findComponent({ name: 'Navbar' }).vm.$emit('admin')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches logout to store', () => {
|
||||||
|
expect(storeDispatchMock).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('changes window location to admin interface', () => {
|
||||||
|
expect(windowLocationMock).toBeCalledWith(
|
||||||
|
'http://localhost/admin/authenticate?token=valid-token',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,95 +1,64 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<side-bar @logout="logout" :balance="balance" :pending="pending">
|
<navbar
|
||||||
<template slot="links">
|
class="main-navbar"
|
||||||
<sidebar-item
|
:balance="balance"
|
||||||
:link="{
|
:visible="visible"
|
||||||
name: $t('overview'),
|
:elopageUri="elopageUri"
|
||||||
path: '/overview',
|
@set-visible="setVisible"
|
||||||
}"
|
@admin="admin"
|
||||||
></sidebar-item>
|
@logout="logout"
|
||||||
<sidebar-item
|
/>
|
||||||
:link="{
|
<div class="content-gradido">
|
||||||
name: $t('send'),
|
<div class="d-none d-sm-none d-md-none d-lg-flex shadow-lg" style="width: 300px">
|
||||||
path: '/send',
|
<sidebar class="main-sidebar" :elopageUri="elopageUri" @admin="admin" @logout="logout" />
|
||||||
}"
|
|
||||||
></sidebar-item>
|
|
||||||
<sidebar-item
|
|
||||||
:link="{
|
|
||||||
name: $t('transactions'),
|
|
||||||
path: '/transactions',
|
|
||||||
}"
|
|
||||||
></sidebar-item>
|
|
||||||
<sidebar-item
|
|
||||||
:link="{
|
|
||||||
name: $t('site.navbar.my-profil'),
|
|
||||||
path: '/profile',
|
|
||||||
}"
|
|
||||||
></sidebar-item>
|
|
||||||
</template>
|
|
||||||
</side-bar>
|
|
||||||
<div class="main-content" style="max-width: 1000px">
|
|
||||||
<div class="d-none d-md-block">
|
|
||||||
<b-navbar>
|
|
||||||
<b-navbar-nav class="ml-auto">
|
|
||||||
<b-nav-item>
|
|
||||||
<b-media no-body class="align-items-center">
|
|
||||||
<span class="pb-2 text-lg font-weight-bold">
|
|
||||||
{{ $store.state.email }}
|
|
||||||
</span>
|
|
||||||
<b-media-body class="ml-2">
|
|
||||||
<span class="avatar">
|
|
||||||
<vue-qrcode
|
|
||||||
v-if="$store.state.email"
|
|
||||||
:value="$store.state.email"
|
|
||||||
type="image/png"
|
|
||||||
></vue-qrcode>
|
|
||||||
</span>
|
|
||||||
</b-media-body>
|
|
||||||
</b-media>
|
|
||||||
</b-nav-item>
|
|
||||||
</b-navbar-nav>
|
|
||||||
</b-navbar>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div @click="$sidebar.displaySidebar(false)">
|
<div class="main-page ml-2 mr-2" style="width: 100%" @click="visible = false">
|
||||||
<fade-transition :duration="200" origin="center top" mode="out-in">
|
<div class="main-content">
|
||||||
<router-view
|
<fade-transition :duration="200" origin="center top" mode="out-in">
|
||||||
ref="router-view"
|
<router-view
|
||||||
:balance="balance"
|
ref="router-view"
|
||||||
:gdt-balance="GdtBalance"
|
:balance="balance"
|
||||||
:transactions="transactions"
|
:gdt-balance="GdtBalance"
|
||||||
:transactionCount="transactionCount"
|
:transactions="transactions"
|
||||||
:pending="pending"
|
:transactionCount="transactionCount"
|
||||||
@update-balance="updateBalance"
|
:pending="pending"
|
||||||
@update-transactions="updateTransactions"
|
@update-balance="updateBalance"
|
||||||
></router-view>
|
@update-transactions="updateTransactions"
|
||||||
</fade-transition>
|
></router-view>
|
||||||
|
</fade-transition>
|
||||||
|
</div>
|
||||||
|
<content-footer v-if="!$route.meta.hideFooter"></content-footer>
|
||||||
</div>
|
</div>
|
||||||
<content-footer v-if="!$route.meta.hideFooter"></content-footer>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import Navbar from '../../components/Menu/Navbar.vue'
|
||||||
|
import Sidebar from '../../components/Menu/Sidebar.vue'
|
||||||
import { logout, transactionsQuery } from '../../graphql/queries'
|
import { logout, transactionsQuery } from '../../graphql/queries'
|
||||||
import ContentFooter from './ContentFooter.vue'
|
import ContentFooter from './ContentFooter.vue'
|
||||||
import { FadeTransition } from 'vue2-transitions'
|
import { FadeTransition } from 'vue2-transitions'
|
||||||
import VueQrcode from 'vue-qrcode'
|
import CONFIG from '../../config'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Navbar,
|
||||||
|
Sidebar,
|
||||||
ContentFooter,
|
ContentFooter,
|
||||||
VueQrcode,
|
|
||||||
FadeTransition,
|
FadeTransition,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
logo: 'img/brand/green.png',
|
||||||
balance: 0,
|
balance: 0,
|
||||||
GdtBalance: 0,
|
GdtBalance: 0,
|
||||||
transactions: [],
|
transactions: [],
|
||||||
bookedBalance: 0,
|
bookedBalance: 0,
|
||||||
transactionCount: 0,
|
transactionCount: 0,
|
||||||
pending: true,
|
pending: true,
|
||||||
|
visible: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -99,12 +68,10 @@ export default {
|
|||||||
query: logout,
|
query: logout,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$sidebar.displaySidebar(false)
|
|
||||||
this.$store.dispatch('logout')
|
this.$store.dispatch('logout')
|
||||||
this.$router.push('/login')
|
this.$router.push('/login')
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.$sidebar.displaySidebar(false)
|
|
||||||
this.$store.dispatch('logout')
|
this.$store.dispatch('logout')
|
||||||
if (this.$router.currentRoute.path !== '/login') this.$router.push('/login')
|
if (this.$router.currentRoute.path !== '/login') this.$router.push('/login')
|
||||||
})
|
})
|
||||||
@ -140,6 +107,40 @@ export default {
|
|||||||
updateBalance(ammount) {
|
updateBalance(ammount) {
|
||||||
this.balance -= ammount
|
this.balance -= ammount
|
||||||
},
|
},
|
||||||
|
admin() {
|
||||||
|
window.location.assign(CONFIG.ADMIN_AUTH_URL.replace('$1', this.$store.state.token))
|
||||||
|
this.$store.dispatch('logout') // logout without redirect
|
||||||
|
},
|
||||||
|
setVisible(bool) {
|
||||||
|
this.visible = bool
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
elopageUri() {
|
||||||
|
const pId = this.$store.state.publisherId
|
||||||
|
? this.$store.state.publisherId
|
||||||
|
: CONFIG.DEFAULT_PUBLISHER_ID
|
||||||
|
return encodeURI(
|
||||||
|
this.$store.state.hasElopage
|
||||||
|
? `https://elopage.com/s/gradido/sign_in?locale=${this.$i18n.locale}`
|
||||||
|
: `https://elopage.com/s/gradido/basic-de/payment?locale=${this.$i18n.locale}&prid=111&pid=${pId}&firstName=${this.$store.state.firstName}&lastName=${this.$store.state.lastName}&email=${this.$store.state.email}`,
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.content-gradido {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 91%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.navbar-brand-img {
|
||||||
|
height: 2rem;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.bg-lightgrey {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<b-container fluid>
|
<div>
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col class="col-6">
|
<b-col class="col-6">
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col class="col-11 bg-gray text-white p-3">
|
<b-col class="col-11 ml-2 p-3 bg-lightgrey">
|
||||||
<status
|
<status
|
||||||
class="gdd-status-gdd"
|
class="gdd-status-gdd"
|
||||||
:pending="pending"
|
:pending="pending"
|
||||||
@ -14,9 +14,9 @@
|
|||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col class="col-6 text-right">
|
<b-col class="col-6 text-right bg-lightgrey">
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col class="bg-white text-gray p-3">
|
<b-col class="p-3">
|
||||||
<status
|
<status
|
||||||
class="gdd-status-gdt"
|
class="gdd-status-gdt"
|
||||||
:pending="pending"
|
:pending="pending"
|
||||||
@ -36,7 +36,7 @@
|
|||||||
@update-transactions="updateTransactions"
|
@update-transactions="updateTransactions"
|
||||||
/>
|
/>
|
||||||
<gdd-transaction-list-footer :count="transactionCount" />
|
<gdd-transaction-list-footer :count="transactionCount" />
|
||||||
</b-container>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -36,9 +36,42 @@ describe('GddTransactionList', () => {
|
|||||||
expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy()
|
expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('no transactions from server', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.setProps({
|
||||||
|
transactions: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('shows error no transaction list', () => {
|
||||||
|
expect(wrapper.find('div.test-no-transactionlist').text()).toContain(
|
||||||
|
'error.no-transactionlist',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('0 transactions from server', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.setProps({
|
||||||
|
transactions: [],
|
||||||
|
transactionCount: 0,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('Transactions Array is empty, 0 transactions', () => {
|
||||||
|
expect(wrapper.find('div.test-empty-transactionlist').text()).toContain(
|
||||||
|
'error.empty-transactionlist',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('without any properties', () => {
|
describe('without any properties', () => {
|
||||||
it('renders text saying that there are no transactions', () => {
|
it('renders text saying that there are error.empty-transactionlist ', () => {
|
||||||
expect(wrapper.find('div.gdd-transaction-list').text()).toBe('transaction.nullTransactions')
|
expect(wrapper.find('div.gdd-transaction-list').text()).toContain(
|
||||||
|
'error.empty-transactionlist',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('renders text saying that there are no transaction.nullTransactions', () => {
|
||||||
|
expect(wrapper.find('div.gdd-transaction-list').text()).toContain(
|
||||||
|
'transaction.nullTransactions',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="gdd-transaction-list">
|
<div class="gdd-transaction-list">
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
|
<div v-if="!transactions" class="test-no-transactionlist text-right">
|
||||||
|
<b-icon icon="exclamation-triangle" class="mr-2" style="color: red"></b-icon>
|
||||||
|
<small>
|
||||||
|
{{ $t('error.no-transactionlist') }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div v-if="!transactionCount" class="test-empty-transactionlist text-right">
|
||||||
|
<b-icon icon="exclamation-triangle" class="mr-2" style="color: red"></b-icon>
|
||||||
|
<small>{{ $t('error.empty-transactionlist') }}</small>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="{ decay, transactionId, type, date, balance, name, memo } in transactions"
|
v-for="{ decay, transactionId, type, date, balance, name, memo } in transactions"
|
||||||
:key="transactionId"
|
:key="transactionId"
|
||||||
|
|||||||
@ -13,7 +13,9 @@ describe('SendOverview', () => {
|
|||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
balance: 123.45,
|
balance: 123.45,
|
||||||
transactionCount: 1,
|
GdtBalance: 1234.56,
|
||||||
|
transactions: [{ balance: 0.1 }],
|
||||||
|
pending: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
@ -38,26 +40,20 @@ describe('SendOverview', () => {
|
|||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a status GDD line gdd-status-gdd', () => {
|
|
||||||
expect(wrapper.find('div.gdd-status-gdd').exists()).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a send field', () => {
|
it('has a send field', () => {
|
||||||
expect(wrapper.find('div.gdd-send').exists()).toBeTruthy()
|
expect(wrapper.find('div.gdd-send').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
// it('has a transactions table', () => {
|
|
||||||
// expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy()
|
|
||||||
// })
|
|
||||||
|
|
||||||
describe('transaction form', () => {
|
describe('transaction form', () => {
|
||||||
it('steps forward in the dialog', async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.findComponent({ name: 'TransactionForm' }).vm.$emit('set-transaction', {
|
wrapper.findComponent({ name: 'TransactionForm' }).vm.$emit('set-transaction', {
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
amount: 23.45,
|
amount: 23.45,
|
||||||
memo: 'Make the best of it!',
|
memo: 'Make the best of it!',
|
||||||
})
|
})
|
||||||
expect(wrapper.findComponent({ name: 'TransactionConfirmation' }).exists()).toBeTruthy()
|
})
|
||||||
|
it('steps forward in the dialog', () => {
|
||||||
|
expect(wrapper.findComponent({ name: 'TransactionConfirmation' }).exists()).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -116,18 +112,22 @@ describe('SendOverview', () => {
|
|||||||
describe('transaction is confirmed and server response is error', () => {
|
describe('transaction is confirmed and server response is error', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
sendMock.mockRejectedValue({ message: 'receiver not found' })
|
sendMock.mockRejectedValue({ message: 'recipiant not known' })
|
||||||
await wrapper
|
await wrapper
|
||||||
.findComponent({ name: 'TransactionConfirmation' })
|
.findComponent({ name: 'TransactionConfirmation' })
|
||||||
.vm.$emit('send-transaction')
|
.vm.$emit('send-transaction')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows the error page', () => {
|
it('shows the error page', () => {
|
||||||
expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_error')
|
expect(wrapper.find('.test-send_transaction_error').text()).toContain(
|
||||||
|
'form.send_transaction_error',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows recipient not found', () => {
|
it('shows recipient not found', () => {
|
||||||
expect(wrapper.text()).toContain('transaction.receiverNotFound')
|
expect(wrapper.find('.test-receiver-not-found').text()).toContain(
|
||||||
|
'transaction.receiverNotFound',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,25 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<b-container fluid>
|
<b-container>
|
||||||
<b-row>
|
<gdd-send :currentTransactionStep="currentTransactionStep" class="pt-3">
|
||||||
<b-col class="bg-gray text-white text-center p-3">
|
|
||||||
<status
|
|
||||||
class="gdd-status-gdd"
|
|
||||||
v-if="showContext"
|
|
||||||
:pending="pending"
|
|
||||||
:balance="balance"
|
|
||||||
status-text="GDD"
|
|
||||||
/>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<gdd-send :currentTransactionStep="currentTransactionStep">
|
|
||||||
<template #transaction-form>
|
<template #transaction-form>
|
||||||
<transaction-form :balance="balance" @set-transaction="setTransaction"></transaction-form>
|
<transaction-form :balance="balance" @set-transaction="setTransaction"></transaction-form>
|
||||||
</template>
|
</template>
|
||||||
<template #transaction-confirmation>
|
<template #transaction-confirmation>
|
||||||
<transaction-confirmation
|
<transaction-confirmation
|
||||||
|
:balance="balance"
|
||||||
|
:transactions="transactions"
|
||||||
:email="transactionData.email"
|
:email="transactionData.email"
|
||||||
:amount="transactionData.amount"
|
:amount="transactionData.amount"
|
||||||
:memo="transactionData.memo"
|
:memo="transactionData.memo"
|
||||||
@ -41,9 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Status from '../../components/Status.vue'
|
|
||||||
import GddSend from './SendOverview/GddSend.vue'
|
import GddSend from './SendOverview/GddSend.vue'
|
||||||
|
|
||||||
import TransactionForm from './SendOverview/GddSend/TransactionForm.vue'
|
import TransactionForm from './SendOverview/GddSend/TransactionForm.vue'
|
||||||
import TransactionConfirmation from './SendOverview/GddSend/TransactionConfirmation.vue'
|
import TransactionConfirmation from './SendOverview/GddSend/TransactionConfirmation.vue'
|
||||||
import TransactionResult from './SendOverview/GddSend/TransactionResult.vue'
|
import TransactionResult from './SendOverview/GddSend/TransactionResult.vue'
|
||||||
@ -58,9 +45,7 @@ const EMPTY_TRANSACTION_DATA = {
|
|||||||
export default {
|
export default {
|
||||||
name: 'SendOverview',
|
name: 'SendOverview',
|
||||||
components: {
|
components: {
|
||||||
Status,
|
|
||||||
GddSend,
|
GddSend,
|
||||||
|
|
||||||
TransactionForm,
|
TransactionForm,
|
||||||
TransactionConfirmation,
|
TransactionConfirmation,
|
||||||
TransactionResult,
|
TransactionResult,
|
||||||
@ -86,11 +71,6 @@ export default {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
showContext() {
|
|
||||||
return this.currentTransactionStep === 0
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
setTransaction(data) {
|
setTransaction(data) {
|
||||||
this.transactionData = { ...data }
|
this.transactionData = { ...data }
|
||||||
|
|||||||
@ -2,23 +2,61 @@
|
|||||||
<div>
|
<div>
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col>
|
<b-col>
|
||||||
<div class="display-4 p-4">{{ $t('form.send_check') }}</div>
|
<div class="display-4 pb-4">{{ $t('form.send_check') }}</div>
|
||||||
<b-list-group>
|
<b-list-group class="">
|
||||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
<label class="input-1" for="input-1">{{ $t('form.recipient') }}</label>
|
||||||
{{ email }}
|
<b-input-group id="input-group-1" class="borderbottom" size="lg">
|
||||||
<b-badge variant="primary" pill>{{ $t('form.recipient') }}</b-badge>
|
<b-input-group-prepend class="d-none d-md-block gray-background">
|
||||||
</b-list-group-item>
|
<b-icon icon="envelope" class="display-4 m-3"></b-icon>
|
||||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
</b-input-group-prepend>
|
||||||
{{ $n(amount, 'decimal') }} GDD
|
<div class="p-3">{{ email }}</div>
|
||||||
<b-badge variant="primary" pill>{{ $t('form.amount') }}</b-badge>
|
</b-input-group>
|
||||||
</b-list-group-item>
|
<br />
|
||||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
<label class="input-2" for="input-2">{{ $t('form.amount') }}</label>
|
||||||
{{ memo ? memo : '-' }}
|
<b-input-group id="input-group-2" class="borderbottom" size="lg">
|
||||||
<b-badge variant="primary" pill>{{ $t('form.message') }}</b-badge>
|
<b-input-group-prepend class="p-2 d-none d-md-block gray-background">
|
||||||
</b-list-group-item>
|
<div class="m-1 mt-2">GDD</div>
|
||||||
|
</b-input-group-prepend>
|
||||||
|
|
||||||
|
<div class="p-3">{{ $n(amount, 'decimal') }}</div>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<label class="input-3" for="input-3">{{ $t('form.message') }}</label>
|
||||||
|
<b-input-group id="input-group-3" class="borderbottom">
|
||||||
|
<b-input-group-prepend class="d-none d-md-block gray-background">
|
||||||
|
<b-icon icon="chat-right-text" class="display-4 m-3 mt-4"></b-icon>
|
||||||
|
</b-input-group-prepend>
|
||||||
|
<div class="p-3">{{ memo ? memo : '-' }}</div>
|
||||||
|
</b-input-group>
|
||||||
</b-list-group>
|
</b-list-group>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
|
|
||||||
|
<b-container class="bv-example-row mt-3 gray-background p-2">
|
||||||
|
<b-row class="pr-3">
|
||||||
|
<b-col class="text-right">{{ $t('form.current_balance') }}</b-col>
|
||||||
|
<b-col class="text-right">{{ $n(balance, 'decimal') }}</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row class="pr-3">
|
||||||
|
<b-col class="text-right">
|
||||||
|
<strong>{{ $t('form.your_amount') }}</strong>
|
||||||
|
</b-col>
|
||||||
|
<b-col class="text-right">
|
||||||
|
<strong>- {{ $n(amount, 'decimal') }}</strong>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row class="pr-3">
|
||||||
|
<b-col class="text-right">{{ $t('decay.decay') }}</b-col>
|
||||||
|
<b-col class="text-right" style="border-bottom: double">- {{ $n(decay, 'decimal') }}</b-col>
|
||||||
|
</b-row>
|
||||||
|
|
||||||
|
<b-row class="pr-3">
|
||||||
|
<b-col class="text-right">{{ $t('form.new_balance') }}</b-col>
|
||||||
|
<b-col class="text-right">~ {{ $n(balance - amount - decay, 'decimal') }}</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
|
||||||
<b-row class="mt-4">
|
<b-row class="mt-4">
|
||||||
<b-col>
|
<b-col>
|
||||||
<b-button @click="$emit('on-reset')">{{ $t('form.cancel') }}</b-button>
|
<b-button @click="$emit('on-reset')">{{ $t('form.cancel') }}</b-button>
|
||||||
@ -35,10 +73,27 @@
|
|||||||
export default {
|
export default {
|
||||||
name: 'TransactionConfirmation',
|
name: 'TransactionConfirmation',
|
||||||
props: {
|
props: {
|
||||||
|
balance: { type: Number, default: 0 },
|
||||||
email: { type: String, default: '' },
|
email: { type: String, default: '' },
|
||||||
amount: { type: Number, default: 0 },
|
amount: { type: Number, default: 0 },
|
||||||
memo: { type: String, default: '' },
|
memo: { type: String, default: '' },
|
||||||
loading: { type: Boolean, default: false },
|
loading: { type: Boolean, default: false },
|
||||||
|
transactions: {
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
decay: this.transactions[0].balance,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.gray-background {
|
||||||
|
background-color: #ecebe6a3 !important;
|
||||||
|
}
|
||||||
|
.borderbottom {
|
||||||
|
border-bottom: 1px solid rgb(70, 65, 65);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -20,10 +20,10 @@
|
|||||||
<div>{{ $t('form.sorry') }}</div>
|
<div>{{ $t('form.sorry') }}</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div>{{ $t('form.send_transaction_error') }}</div>
|
<div class="test-send_transaction_error">{{ $t('form.send_transaction_error') }}</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<div v-if="errorResult === 'receiver not found'">
|
<div class="test-receiver-not-found" v-if="errorResult === 'recipiant not known'">
|
||||||
{{ $t('transaction.receiverNotFound') }}
|
{{ $t('transaction.receiverNotFound') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>({{ errorResult }})</div>
|
<div v-else>({{ errorResult }})</div>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import * as rules from 'vee-validate/dist/rules'
|
|||||||
import { messages } from 'vee-validate/dist/locale/en.json'
|
import { messages } from 'vee-validate/dist/locale/en.json'
|
||||||
|
|
||||||
import RegeneratorRuntime from 'regenerator-runtime'
|
import RegeneratorRuntime from 'regenerator-runtime'
|
||||||
import SideBar from '@/components/SidebarPlugin'
|
|
||||||
import VueQrcode from 'vue-qrcode'
|
import VueQrcode from 'vue-qrcode'
|
||||||
|
|
||||||
import VueMoment from 'vue-moment'
|
import VueMoment from 'vue-moment'
|
||||||
@ -41,7 +40,6 @@ global.localVue.use(BootstrapVue)
|
|||||||
global.localVue.use(Vuex)
|
global.localVue.use(Vuex)
|
||||||
global.localVue.use(IconsPlugin)
|
global.localVue.use(IconsPlugin)
|
||||||
global.localVue.use(RegeneratorRuntime)
|
global.localVue.use(RegeneratorRuntime)
|
||||||
global.localVue.use(SideBar)
|
|
||||||
global.localVue.use(VueQrcode)
|
global.localVue.use(VueQrcode)
|
||||||
global.localVue.use(VueMoment)
|
global.localVue.use(VueMoment)
|
||||||
global.localVue.component('validation-provider', ValidationProvider)
|
global.localVue.component('validation-provider', ValidationProvider)
|
||||||
|
|||||||
@ -3325,11 +3325,6 @@ babel-plugin-polyfill-regenerator@^0.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-define-polyfill-provider" "^0.1.2"
|
"@babel/helper-define-polyfill-provider" "^0.1.2"
|
||||||
|
|
||||||
babel-plugin-require-context-hook@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-require-context-hook/-/babel-plugin-require-context-hook-1.0.0.tgz#3f0e7cce87c338f53639b948632fd4e73834632d"
|
|
||||||
integrity sha512-EMZD1563QUqLhzrqcThk759RhuNVX/ZJdrtGK6drwzgvnR+ARjWyXIHPbu+tUNaMGtPz/gQeAM2M6VUw2UiUeA==
|
|
||||||
|
|
||||||
babel-plugin-syntax-jsx@^6.18.0:
|
babel-plugin-syntax-jsx@^6.18.0:
|
||||||
version "6.18.0"
|
version "6.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
|
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user