mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
KlickTipp subscribe and unsubscribe, merge master.
This commit is contained in:
commit
ad1aa402f4
@ -18,6 +18,7 @@
|
||||
"apollo-server-express": "^2.25.2",
|
||||
"axios": "^0.21.1",
|
||||
"class-validator": "^0.13.1",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^15.5.1",
|
||||
|
||||
@ -8,8 +8,8 @@ const klicktippConnector = new KlicktippConnector()
|
||||
export const signin = async (
|
||||
email: string,
|
||||
language: string,
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
firstName?: string,
|
||||
lastName?: string,
|
||||
): Promise<boolean> => {
|
||||
const fields = {
|
||||
fieldFirstName: firstName,
|
||||
@ -27,7 +27,11 @@ export const signout = async (email: string, language: string): Promise<boolean>
|
||||
}
|
||||
|
||||
export const unsubscribe = async (email: string): Promise<boolean> => {
|
||||
return await klicktippConnector.unsubscribe(email)
|
||||
const isLogin = await loginKlicktippUser()
|
||||
if (isLogin) {
|
||||
return await klicktippConnector.unsubscribe(email)
|
||||
}
|
||||
throw new Error(`Could not unsubscribe ${email}`)
|
||||
}
|
||||
|
||||
export const getKlickTippUser = async (email: string): Promise<any> => {
|
||||
|
||||
@ -4,6 +4,7 @@ import { AuthChecker } from 'type-graphql'
|
||||
import decode from '../jwt/decode'
|
||||
import { apiGet } from '../apis/HttpRequest'
|
||||
import CONFIG from '../config'
|
||||
import encode from '../jwt/encode'
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||
export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info }, roles) => {
|
||||
@ -14,6 +15,7 @@ export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info
|
||||
`${CONFIG.LOGIN_API_URL}checkSessionState?session_id=${decoded.sessionId}`,
|
||||
)
|
||||
context.sessionId = decoded.sessionId
|
||||
context.setHeaders.push({ key: 'token', value: encode(decoded.sessionId) })
|
||||
return result.success
|
||||
}
|
||||
}
|
||||
|
||||
10
backend/src/graphql/inputs/KlickTippInputs.ts
Normal file
10
backend/src/graphql/inputs/KlickTippInputs.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { ArgsType, Field } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export class SubscribeNewsletterArguments {
|
||||
@Field(() => String)
|
||||
email: string
|
||||
|
||||
@Field(() => String)
|
||||
language: string
|
||||
}
|
||||
13
backend/src/graphql/models/KlickTipp.ts
Normal file
13
backend/src/graphql/models/KlickTipp.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class KlickTipp {
|
||||
constructor(json: any) {
|
||||
this.newsletterState = json.status !== 'unsubscribed'
|
||||
}
|
||||
|
||||
@Field(() => Boolean)
|
||||
newsletterState: boolean
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { KlickTipp } from './KlickTipp'
|
||||
|
||||
@ObjectType()
|
||||
export class User {
|
||||
@ -64,4 +65,7 @@ export class User {
|
||||
@Field(() => ID)
|
||||
publisherId: number
|
||||
*/
|
||||
|
||||
@Field(() => KlickTipp)
|
||||
klickTipp: KlickTipp
|
||||
}
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { Resolver, Query, Authorized, Arg } from 'type-graphql'
|
||||
import { getKlickTippUser, getKlicktippTagMap } from '../../apis/KlicktippController'
|
||||
import CONFIG from '../../config'
|
||||
import { TransactionList } from '../models/Transaction'
|
||||
import { Resolver, Query, Authorized, Arg, Mutation, Args } from 'type-graphql'
|
||||
import {
|
||||
getKlickTippUser,
|
||||
getKlicktippTagMap,
|
||||
unsubscribe,
|
||||
signin,
|
||||
} from '../../apis/KlicktippController'
|
||||
import { SubscribeNewsletterArguments } from '../inputs/KlickTippInputs'
|
||||
|
||||
@Resolver()
|
||||
export class KlicktippResolver {
|
||||
@ -17,4 +21,16 @@ export class KlicktippResolver {
|
||||
async getKlicktippTagMap(): Promise<string> {
|
||||
return await getKlicktippTagMap()
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async unsubscribeNewsletter(@Arg('email') email: string): Promise<boolean> {
|
||||
return await unsubscribe(email)
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async subscribeNewsletter(
|
||||
@Args() { email, language }: SubscribeNewsletterArguments,
|
||||
): Promise<boolean> {
|
||||
return await signin(email, language)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,10 +4,11 @@
|
||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware } from 'type-graphql'
|
||||
import CONFIG from '../../config'
|
||||
import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
|
||||
import { User } from '../models/User'
|
||||
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
|
||||
import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse'
|
||||
import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse'
|
||||
import { User } from '../models/User'
|
||||
import encode from '../../jwt/encode'
|
||||
import {
|
||||
ChangePasswordArgs,
|
||||
CheckUsernameArgs,
|
||||
@ -20,14 +21,12 @@ import {
|
||||
klicktippRegistrationMiddleware,
|
||||
klicktippNewsletterStateMiddleware,
|
||||
} from '../../middleware/klicktippMiddleware'
|
||||
import encode from '../../jwt/encode'
|
||||
import { CheckEmailResponse } from '../models/CheckEmailResponse'
|
||||
|
||||
@Resolver()
|
||||
export class UserResolver {
|
||||
@Query(() => String)
|
||||
@Query(() => User)
|
||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||
async login(@Args() { email, password }: UnsecureLoginArgs): Promise<string> {
|
||||
async login(@Args() { email, password }: UnsecureLoginArgs, @Ctx() context: any): Promise<User> {
|
||||
email = email.trim().toLowerCase()
|
||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
||||
|
||||
@ -36,10 +35,9 @@ export class UserResolver {
|
||||
throw new Error(result.data)
|
||||
}
|
||||
|
||||
const data = result.data
|
||||
const sessionId = data.session_id
|
||||
delete data.session_id
|
||||
return encode({ sessionId, user: new User(data.user) })
|
||||
context.setHeaders.push({ key: 'token', value: encode(result.data.session_id) })
|
||||
|
||||
return new User(result.data.user)
|
||||
}
|
||||
|
||||
@Query(() => LoginViaVerificationCode)
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import 'reflect-metadata'
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { buildSchema } from 'type-graphql'
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { RowDataPacket } from 'mysql2/promise'
|
||||
@ -23,14 +24,15 @@ import { isAuthorized } from './auth/auth'
|
||||
|
||||
const DB_VERSION = '0001-init_db'
|
||||
|
||||
const context = (req: any) => {
|
||||
const authorization = req.req.headers.authorization
|
||||
const context = (args: any) => {
|
||||
const authorization = args.req.headers.authorization
|
||||
let token = null
|
||||
if (authorization) {
|
||||
token = req.req.headers.authorization.replace(/^Bearer /, '')
|
||||
token = authorization.replace(/^Bearer /, '')
|
||||
}
|
||||
const context = {
|
||||
token,
|
||||
setHeaders: [],
|
||||
}
|
||||
return context
|
||||
}
|
||||
@ -62,8 +64,31 @@ async function main() {
|
||||
// Express Server
|
||||
const server = express()
|
||||
|
||||
const corsOptions = {
|
||||
origin: '*',
|
||||
exposedHeaders: ['token'],
|
||||
}
|
||||
|
||||
server.use(cors(corsOptions))
|
||||
|
||||
const plugins = [
|
||||
{
|
||||
requestDidStart() {
|
||||
return {
|
||||
willSendResponse(requestContext: any) {
|
||||
const { setHeaders = [] } = requestContext.context
|
||||
setHeaders.forEach(({ key, value }: { [key: string]: string }) => {
|
||||
requestContext.response.http.headers.append(key, value)
|
||||
})
|
||||
return requestContext
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// Apollo Server
|
||||
const apollo = new ApolloServer({ schema, playground, context })
|
||||
const apollo = new ApolloServer({ schema, playground, context, plugins })
|
||||
apollo.applyMiddleware({ app: server })
|
||||
|
||||
// Start Server
|
||||
|
||||
@ -7,15 +7,12 @@ import CONFIG from '../config/'
|
||||
export default (token: string): any => {
|
||||
if (!token) return null
|
||||
let sessionId = null
|
||||
const email = null
|
||||
try {
|
||||
const decoded = jwt.verify(token, CONFIG.JWT_SECRET)
|
||||
sessionId = decoded.sub
|
||||
// email = decoded.email
|
||||
return {
|
||||
token,
|
||||
sessionId,
|
||||
email,
|
||||
}
|
||||
} catch (err) {
|
||||
return null
|
||||
|
||||
@ -5,13 +5,9 @@ import jwt from 'jsonwebtoken'
|
||||
import CONFIG from '../config/'
|
||||
|
||||
// Generate an Access Token
|
||||
export default function encode(data: any): string {
|
||||
const { user, sessionId } = data
|
||||
const { email, language, firstName, lastName } = user
|
||||
const token = jwt.sign({ email, language, firstName, lastName, sessionId }, CONFIG.JWT_SECRET, {
|
||||
export default function encode(sessionId: string): string {
|
||||
const token = jwt.sign({ sessionId }, CONFIG.JWT_SECRET, {
|
||||
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||
// issuer: CONFIG.GRAPHQL_URI,
|
||||
// audience: CONFIG.CLIENT_URI,
|
||||
subject: sessionId.toString(),
|
||||
})
|
||||
return token
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { MiddlewareFn } from 'type-graphql'
|
||||
import { signin, getKlickTippUser } from '../apis/KlicktippController'
|
||||
import { KlickTipp } from '../graphql/models/KlickTipp'
|
||||
import decode from '../jwt/decode'
|
||||
|
||||
export const klicktippRegistrationMiddleware: MiddlewareFn = async (
|
||||
@ -20,9 +21,11 @@ export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
|
||||
next,
|
||||
) => {
|
||||
const result = await next()
|
||||
const decodedResult = decode(result)
|
||||
console.log('result', decodedResult)
|
||||
const klickTippUser = getKlickTippUser(decodedResult.email)
|
||||
const klickTippUser = await getKlickTippUser(result.email)
|
||||
console.log('klickTippUser', klickTippUser)
|
||||
const klickTipp = new KlickTipp(klickTippUser)
|
||||
console.log('klickTipp', klickTipp)
|
||||
result.klickTipp = klickTipp
|
||||
console.log('result', result)
|
||||
return result
|
||||
}
|
||||
|
||||
@ -50,8 +50,8 @@ $this->assign('header', $header);
|
||||
<?php if(intval($entry['gdt_entry_type_id']) == 7) : ?>
|
||||
<?= $this->element('printGDT', ['number' => $entry['amount']*100.0]); ?>
|
||||
<?php else : ?>
|
||||
<?= $this->element('printEuro', ['number' => $entry['amount']]); ?>
|
||||
<?php if($entry['amount2']) echo ' + ' . $this->element('printEuro', ['number' => $entry['amount2']]) ?>
|
||||
<?= $this->element('printEuro', ['number' => $entry['amount']*100.0]); ?>
|
||||
<?php if($entry['amount2']) echo ' + ' . $this->element('printEuro', ['number' => $entry['amount2']*100.0]) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="cell c2">
|
||||
@ -96,8 +96,8 @@ $this->assign('header', $header);
|
||||
<?php if(intval($gdtEntry['gdt_entry_type_id']) == 7) : ?>
|
||||
<?= $this->element('printGDT', ['number' => $gdtEntry['amount']*100.0]); ?>
|
||||
<?php else : ?>
|
||||
<?= $this->element('printEuro', ['number' => $gdtEntry['amount']]); ?>
|
||||
<?php if($gdtEntry['amount2']) echo ' + ' . $this->element('printEuro', ['number' => $gdtEntry['amount2']]) ?>
|
||||
<?= $this->element('printEuro', ['number' => $gdtEntry['amount']*100.0]); ?>
|
||||
<?php if($gdtEntry['amount2']) echo ' + ' . $this->element('printEuro', ['number' => $gdtEntry['amount2']*100.0]) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="cell c2">
|
||||
|
||||
@ -72,7 +72,6 @@
|
||||
"vue-good-table": "^2.21.3",
|
||||
"vue-i18n": "^8.22.4",
|
||||
"vue-jest": "^3.0.7",
|
||||
"vue-jwt-decode": "^0.1.0",
|
||||
"vue-loading-overlay": "^3.4.2",
|
||||
"vue-moment": "^4.1.0",
|
||||
"vue-qrcode": "^0.3.5",
|
||||
|
||||
40
frontend/src/components/LanguageSwitchSelect.vue
Normal file
40
frontend/src/components/LanguageSwitchSelect.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="language-switch-select">
|
||||
<b-form-select
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
class="selectedLanguage mb-3"
|
||||
></b-form-select>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'LanguageSwitch',
|
||||
data() {
|
||||
return {
|
||||
selected: null,
|
||||
options: [
|
||||
{ value: null, text: this.$t('select_language') },
|
||||
{ value: 'de', text: this.$t('languages.de') },
|
||||
{ value: 'en', text: this.$t('languages.en') },
|
||||
],
|
||||
}
|
||||
},
|
||||
props: {
|
||||
language: { type: String },
|
||||
},
|
||||
created() {
|
||||
this.selected = this.$store.state.language
|
||||
},
|
||||
computed: {
|
||||
languageObject() {
|
||||
return this.selected
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selected() {
|
||||
this.$emit('update-language', this.languageObject)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
31
frontend/src/components/Transaction.spec.js
Normal file
31
frontend/src/components/Transaction.spec.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Transaction from './Transaction'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('Transaction', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$t: jest.fn((t) => t),
|
||||
$n: jest.fn((n) => n),
|
||||
$d: jest.fn((d) => d),
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(Transaction, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div.gdt-transaction-list-item').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
124
frontend/src/components/Transaction.vue
Normal file
124
frontend/src/components/Transaction.vue
Normal file
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item gdt-transaction-list-item" v-b-toggle="'a' + date + ''">
|
||||
<!-- icon -->
|
||||
<div class="text-right" style="position: absolute">
|
||||
<b-icon
|
||||
:icon="getLinesByType(gdtEntryType).icon"
|
||||
:class="getLinesByType(gdtEntryType).iconclasses"
|
||||
></b-icon>
|
||||
</div>
|
||||
|
||||
<!-- collaps Button -->
|
||||
<div class="text-right" style="width: 96%; position: absolute">
|
||||
<b-button class="btn-sm">
|
||||
<b>i</b>
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
<!-- type -->
|
||||
<b-row>
|
||||
<div class="col-6 text-right">
|
||||
{{ getLinesByType(gdtEntryType).description }}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
{{ getLinesByType(gdtEntryType).descriptiontext }}
|
||||
</div>
|
||||
</b-row>
|
||||
|
||||
<!-- credit -->
|
||||
<b-row>
|
||||
<div class="col-6 text-right">
|
||||
{{ $t('gdt.credit') }}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
{{ getLinesByType(gdtEntryType).credittext }}
|
||||
</div>
|
||||
</b-row>
|
||||
|
||||
<!-- Message-->
|
||||
<b-row v-if="comment && gdtEntryType !== 7">
|
||||
<div class="col-6 text-right">
|
||||
{{ $t('form.memo') }}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
{{ comment }}
|
||||
</div>
|
||||
</b-row>
|
||||
|
||||
<!-- date-->
|
||||
<b-row class="gdt-list-row text-header">
|
||||
<div class="col-6 text-right">
|
||||
{{ $t('form.date') }}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
{{ $d($moment(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
|
||||
</div>
|
||||
</b-row>
|
||||
</div>
|
||||
|
||||
<!-- collaps trancaction info-->
|
||||
<b-collapse :id="'a' + date + ''" class="pb-4">
|
||||
<transaction-collapse
|
||||
:amount="amount"
|
||||
:gdtEntryType="gdtEntryType"
|
||||
:factor="factor"
|
||||
:gdt="gdt"
|
||||
></transaction-collapse>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import TransactionCollapse from './TransactionCollapse.vue'
|
||||
|
||||
export default {
|
||||
name: 'Transaction',
|
||||
components: {
|
||||
TransactionCollapse,
|
||||
},
|
||||
props: {
|
||||
amount: { type: Number },
|
||||
date: { type: String },
|
||||
comment: { type: String },
|
||||
gdtEntryType: { type: Number, default: 1 },
|
||||
factor: { type: Number },
|
||||
gdt: { type: Number },
|
||||
},
|
||||
methods: {
|
||||
getLinesByType(givenType) {
|
||||
if (givenType === 2 || givenType === 3 || givenType === 5 || givenType === 6) givenType = 1
|
||||
|
||||
const linesByType = {
|
||||
1: {
|
||||
icon: 'heart',
|
||||
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||
description: this.$t('gdt.contribution'),
|
||||
descriptiontext: this.$n(this.amount, 'decimal') + ' €',
|
||||
credittext: this.$n(this.gdt, 'decimal') + ' GDT',
|
||||
},
|
||||
4: {
|
||||
icon: 'person-check',
|
||||
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||
description: this.$t('gdt.recruited-member'),
|
||||
descriptiontext: '5%',
|
||||
credittext: this.$n(this.amount, 'decimal') + ' GDT',
|
||||
},
|
||||
7: {
|
||||
icon: 'gift',
|
||||
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||
description: this.$t('gdt.gdt-received'),
|
||||
descriptiontext: this.comment,
|
||||
credittext: this.$n(this.gdt, 'decimal') + ' GDT',
|
||||
},
|
||||
}
|
||||
|
||||
const type = linesByType[givenType]
|
||||
|
||||
if (type) return type
|
||||
throw new Error('no lines for this type: ' + givenType)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
152
frontend/src/components/TransactionCollapse.spec.js
Normal file
152
frontend/src/components/TransactionCollapse.spec.js
Normal file
@ -0,0 +1,152 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import TransactionCollapse from './TransactionCollapse'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('TransactionCollapse', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$n: jest.fn((n) => n),
|
||||
}
|
||||
|
||||
const Wrapper = (propsData) => {
|
||||
return mount(TransactionCollapse, { localVue, mocks, propsData })
|
||||
}
|
||||
|
||||
describe('mount with gdtEntryType: 1', () => {
|
||||
beforeEach(() => {
|
||||
const propsData = {
|
||||
amount: 100,
|
||||
gdt: 110,
|
||||
factor: 22,
|
||||
gdtEntryType: 1,
|
||||
}
|
||||
|
||||
wrapper = Wrapper(propsData)
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div.gdt-transaction-collapse').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checks the prop gdtEntryType ', () => {
|
||||
expect(wrapper.props().gdtEntryType).toBe(1)
|
||||
})
|
||||
|
||||
it('renders the component collapse-header', () => {
|
||||
expect(wrapper.find('.gdt-list-collapse-header-text')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the component collapse-headline', () => {
|
||||
expect(wrapper.find('#collapse-headline').text()).toBe('gdt.calculation')
|
||||
})
|
||||
|
||||
it('renders the component collapse-first', () => {
|
||||
expect(wrapper.find('#collapse-first').text()).toBe('gdt.factor')
|
||||
})
|
||||
|
||||
it('renders the component collapse-second', () => {
|
||||
expect(wrapper.find('#collapse-second').text()).toBe('gdt.formula')
|
||||
})
|
||||
|
||||
it('renders the component collapse-firstMath', () => {
|
||||
expect(wrapper.find('#collapse-firstMath').text()).toBe('22 GDT pro €')
|
||||
})
|
||||
|
||||
it('renders the component collapse-secondMath', () => {
|
||||
expect(wrapper.find('#collapse-secondMath').text()).toBe('100 € * 22 GDT / € = 110 GDT')
|
||||
})
|
||||
})
|
||||
|
||||
describe('mount with gdtEntryType: 7', () => {
|
||||
beforeEach(() => {
|
||||
const propsData = {
|
||||
amount: 100,
|
||||
gdt: 2200,
|
||||
factor: 22,
|
||||
gdtEntryType: 7,
|
||||
}
|
||||
|
||||
wrapper = Wrapper(propsData)
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div.gdt-transaction-collapse').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checks the prop gdtEntryType ', () => {
|
||||
expect(wrapper.props().gdtEntryType).toBe(7)
|
||||
})
|
||||
|
||||
it('renders the component collapse-header', () => {
|
||||
expect(wrapper.find('.gdt-list-collapse-header-text')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the component collapse-headline', () => {
|
||||
expect(wrapper.find('#collapse-headline').text()).toBe('gdt.conversion-gdt-euro')
|
||||
})
|
||||
|
||||
it('renders the component collapse-first', () => {
|
||||
expect(wrapper.find('#collapse-first').text()).toBe('gdt.raise')
|
||||
})
|
||||
|
||||
it('renders the component collapse-second', () => {
|
||||
expect(wrapper.find('#collapse-second').text()).toBe('gdt.conversion')
|
||||
})
|
||||
|
||||
it('renders the component collapse-firstMath', () => {
|
||||
expect(wrapper.find('#collapse-firstMath').text()).toBe('2200 %')
|
||||
})
|
||||
|
||||
it('renders the component collapse-secondMath', () => {
|
||||
expect(wrapper.find('#collapse-secondMath').text()).toBe('100 GDT * 2200 % = 2200 GDT')
|
||||
})
|
||||
})
|
||||
|
||||
describe('mount with gdtEntryType: 4', () => {
|
||||
beforeEach(() => {
|
||||
const propsData = {
|
||||
amount: 100,
|
||||
gdt: 2200,
|
||||
factor: 22,
|
||||
gdtEntryType: 4,
|
||||
}
|
||||
|
||||
wrapper = Wrapper(propsData)
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div.gdt-transaction-collapse').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checks the prop gdtEntryType ', () => {
|
||||
expect(wrapper.props().gdtEntryType).toBe(4)
|
||||
})
|
||||
|
||||
it('renders the component collapse-header', () => {
|
||||
expect(wrapper.find('.gdt-list-collapse-header-text')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the component collapse-headline', () => {
|
||||
expect(wrapper.find('#collapse-headline').text()).toBe('gdt.publisher')
|
||||
})
|
||||
|
||||
it('renders the component collapse-first', () => {
|
||||
expect(wrapper.find('#collapse-first').text()).toBe('')
|
||||
})
|
||||
|
||||
it('renders the component collapse-second', () => {
|
||||
expect(wrapper.find('#collapse-second').text()).toBe('')
|
||||
})
|
||||
|
||||
it('renders the component collapse-firstMath', () => {
|
||||
expect(wrapper.find('#collapse-firstMath').text()).toBe('')
|
||||
})
|
||||
|
||||
it('renders the component collapse-secondMath', () => {
|
||||
expect(wrapper.find('#collapse-secondMath').text()).toBe('')
|
||||
})
|
||||
})
|
||||
})
|
||||
76
frontend/src/components/TransactionCollapse.vue
Normal file
76
frontend/src/components/TransactionCollapse.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="gdt-transaction-collapse">
|
||||
<b-row class="gdt-list-collapse-header-text text-center pb-3">
|
||||
<div id="collapse-headline" class="col h4">
|
||||
{{ getLinesByType(gdtEntryType).headline }}
|
||||
</div>
|
||||
</b-row>
|
||||
<b-row class="gdt-list-collapse-box--all">
|
||||
<div class="col-6 text-right collapse-col-left">
|
||||
<div id="collapse-first">{{ getLinesByType(gdtEntryType).first }}</div>
|
||||
<div id="collapse-second">{{ getLinesByType(gdtEntryType).second }}</div>
|
||||
</div>
|
||||
<div class="col-6 collapse-col-right">
|
||||
<div id="collapse-firstMath">{{ getLinesByType(gdtEntryType).firstMath }}</div>
|
||||
<div id="collapse-secondMath">
|
||||
{{ getLinesByType(gdtEntryType).secondMath }}
|
||||
</div>
|
||||
</div>
|
||||
</b-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'TransactionCollapse',
|
||||
props: {
|
||||
amount: { type: Number },
|
||||
gdtEntryType: { type: Number, default: 1 },
|
||||
factor: { type: Number },
|
||||
gdt: { type: Number },
|
||||
},
|
||||
methods: {
|
||||
getLinesByType(givenType) {
|
||||
const linesByType = {
|
||||
1: {
|
||||
headline: this.$t('gdt.calculation'),
|
||||
first: this.$t('gdt.factor'),
|
||||
firstMath: this.factor + ' GDT pro €',
|
||||
second: this.$t('gdt.formula'),
|
||||
secondMath:
|
||||
this.$n(this.amount, 'decimal') +
|
||||
' € * ' +
|
||||
this.factor +
|
||||
' GDT / € = ' +
|
||||
this.$n(this.gdt, 'decimal') +
|
||||
' GDT',
|
||||
},
|
||||
4: {
|
||||
headline: this.$t('gdt.publisher'),
|
||||
first: null,
|
||||
firstMath: null,
|
||||
second: null,
|
||||
secondMath: null,
|
||||
},
|
||||
7: {
|
||||
headline: this.$t('gdt.conversion-gdt-euro'),
|
||||
first: this.$t('gdt.raise'),
|
||||
firstMath: this.factor * 100 + ' % ',
|
||||
second: this.$t('gdt.conversion'),
|
||||
secondMath:
|
||||
this.$n(this.amount, 'decimal') +
|
||||
' GDT * ' +
|
||||
this.factor * 100 +
|
||||
' % = ' +
|
||||
this.$n(this.gdt, 'decimal') +
|
||||
' GDT',
|
||||
},
|
||||
}
|
||||
|
||||
const type = linesByType[givenType]
|
||||
|
||||
if (type) return type
|
||||
throw new Error('no additional transaction info for this type: ' + givenType)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -2,7 +2,17 @@ import gql from 'graphql-tag'
|
||||
|
||||
export const login = gql`
|
||||
query ($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password)
|
||||
login(email: $email, password: $password) {
|
||||
email
|
||||
username
|
||||
firstName
|
||||
lastName
|
||||
language
|
||||
description
|
||||
klickTipp {
|
||||
newsletterState
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@ -64,6 +64,7 @@
|
||||
"change": "ändern",
|
||||
"change-password": "Passwort ändern",
|
||||
"change-name": "Name ändern",
|
||||
"changeLanguage": "Sprache ändern",
|
||||
"amount":"Betrag",
|
||||
"memo":"Nachricht",
|
||||
"message":"Nachricht",
|
||||
@ -142,15 +143,6 @@
|
||||
"send_gradido":"Gradido versenden",
|
||||
"add_work":"neuer Gemeinschaftsbeitrag"
|
||||
},
|
||||
"profil": {
|
||||
"activity": {
|
||||
"new":"Neue Gemeinschaftsstunden eintragen",
|
||||
"list":"Meine Gemeinschaftsstunden Liste"
|
||||
},
|
||||
"user-data": {
|
||||
"change-success": "Deine Daten wurden gespeichert."
|
||||
}
|
||||
},
|
||||
"navbar" : {
|
||||
"my-profil":"Mein Profil",
|
||||
"settings":"Einstellungen",
|
||||
@ -188,8 +180,8 @@
|
||||
"formula":"Berechungsformel",
|
||||
"no-transactions":"Du hast zur Zeit keine Transaktionen",
|
||||
"publisher":"Dein geworbenes Mitglied hat einen Beitrag bezahlt",
|
||||
"gdt-receive":"Aktion",
|
||||
"your-share":"Geworbenes Mitglied",
|
||||
"action":"Aktion",
|
||||
"recruited-member":"Geworbenes Mitglied",
|
||||
"contribution":"Beitrag"
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +64,7 @@
|
||||
"change": "change",
|
||||
"change-password": "Change password",
|
||||
"change-name": "Change name",
|
||||
"changeLanguage": "Change language",
|
||||
"amount":"Amount",
|
||||
"memo":"Message",
|
||||
"message":"Message",
|
||||
@ -142,16 +143,6 @@
|
||||
"send_gradido":"Send Gradido",
|
||||
"add_work":"New Community Contribution"
|
||||
},
|
||||
"profil": {
|
||||
"transactions":"transactions",
|
||||
"activity": {
|
||||
"new":"Register new community hours",
|
||||
"list":"My Community Hours List"
|
||||
},
|
||||
"user-data": {
|
||||
"change-success": "Your data has been saved."
|
||||
}
|
||||
},
|
||||
"navbar" : {
|
||||
"my-profil":"My profile",
|
||||
"settings":"Settings",
|
||||
@ -189,8 +180,8 @@
|
||||
"formula": "Calculation formula",
|
||||
"no-transactions":"You currently have no transactions",
|
||||
"publisher":"A member you referred has paid a contribution",
|
||||
"gdt-receive":"GDT receive",
|
||||
"your-share":"Your share",
|
||||
"action":"Action",
|
||||
"recruited-member":"Recruited Member",
|
||||
"contribution":"Contribution"
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,11 @@ const authLink = new ApolloLink((operation, forward) => {
|
||||
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
||||
},
|
||||
})
|
||||
return forward(operation)
|
||||
return forward(operation).map((response) => {
|
||||
const newToken = operation.getContext().response.headers.get('token')
|
||||
if (newToken) store.commit('token', newToken)
|
||||
return response
|
||||
})
|
||||
})
|
||||
|
||||
const apolloClient = new ApolloClient({
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import createPersistedState from 'vuex-persistedstate'
|
||||
import VueJwtDecode from 'vue-jwt-decode'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
@ -30,15 +29,13 @@ export const mutations = {
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
login: ({ dispatch, commit }, token) => {
|
||||
const decoded = VueJwtDecode.decode(token)
|
||||
commit('token', token)
|
||||
commit('email', decoded.email)
|
||||
commit('language', decoded.language)
|
||||
commit('username', decoded.username)
|
||||
commit('firstName', decoded.firstName)
|
||||
commit('lastName', decoded.lastName)
|
||||
commit('description', decoded.description)
|
||||
login: ({ dispatch, commit }, data) => {
|
||||
commit('email', data.email)
|
||||
commit('language', data.language)
|
||||
commit('username', data.username)
|
||||
commit('firstName', data.firstName)
|
||||
commit('lastName', data.lastName)
|
||||
commit('description', data.description)
|
||||
},
|
||||
logout: ({ commit, state }) => {
|
||||
commit('token', null)
|
||||
|
||||
@ -1,15 +1,4 @@
|
||||
import { mutations, actions } from './store'
|
||||
import VueJwtDecode from 'vue-jwt-decode'
|
||||
|
||||
jest.mock('vue-jwt-decode')
|
||||
VueJwtDecode.decode.mockReturnValue({
|
||||
email: 'user@example.org',
|
||||
language: 'de',
|
||||
username: 'peter',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
description: 'Nickelbrille',
|
||||
})
|
||||
|
||||
const { language, email, token, username, firstName, lastName, description } = mutations
|
||||
const { login, logout } = actions
|
||||
@ -77,46 +66,48 @@ describe('Vuex store', () => {
|
||||
describe('login', () => {
|
||||
const commit = jest.fn()
|
||||
const state = {}
|
||||
const commitedData = 'token'
|
||||
const commitedData = {
|
||||
email: 'user@example.org',
|
||||
language: 'de',
|
||||
username: 'peter',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
description: 'Nickelbrille',
|
||||
}
|
||||
|
||||
it('calls seven commits', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenCalledTimes(7)
|
||||
})
|
||||
|
||||
it('commits token', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'token', 'token')
|
||||
expect(commit).toHaveBeenCalledTimes(6)
|
||||
})
|
||||
|
||||
it('commits email', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'email', 'user@example.org')
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'email', 'user@example.org')
|
||||
})
|
||||
|
||||
it('commits language', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'language', 'de')
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'language', 'de')
|
||||
})
|
||||
|
||||
it('commits username', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'username', 'peter')
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'username', 'peter')
|
||||
})
|
||||
|
||||
it('commits firstName', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(5, 'firstName', 'Peter')
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'firstName', 'Peter')
|
||||
})
|
||||
|
||||
it('commits lastName', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(6, 'lastName', 'Lustig')
|
||||
expect(commit).toHaveBeenNthCalledWith(5, 'lastName', 'Lustig')
|
||||
})
|
||||
|
||||
it('commits description', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(7, 'description', 'Nickelbrille')
|
||||
expect(commit).toHaveBeenNthCalledWith(6, 'description', 'Nickelbrille')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -17,134 +17,14 @@
|
||||
} in transactionsGdt"
|
||||
:key="transactionId"
|
||||
>
|
||||
<div class="list-group-item gdt-transaction-list-item" v-b-toggle="'a' + date + ''">
|
||||
<!-- Icon -->
|
||||
<div class="text-right" style="position: absolute">
|
||||
<b-icon
|
||||
v-if="gdtEntryType"
|
||||
:icon="getIcon(gdtEntryType).icon"
|
||||
:class="getIcon(gdtEntryType).class"
|
||||
></b-icon>
|
||||
</div>
|
||||
|
||||
<!-- Collaps Button -->
|
||||
<div class="text-right" style="width: 96%; position: absolute">
|
||||
<b-button class="btn-sm">
|
||||
<b>i</b>
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
<!-- Betrag -->
|
||||
|
||||
<!-- 7 nur GDT erhalten -->
|
||||
<b-row v-if="gdtEntryType === 7">
|
||||
<div class="col-6 text-right">
|
||||
<div>{{ $t('gdt.gdt-receive') }}</div>
|
||||
<div>{{ $t('gdt.credit') }}</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div>{{ comment }}</div>
|
||||
<div>{{ $n(gdt, 'decimal') }} GDT</div>
|
||||
</div>
|
||||
</b-row>
|
||||
<!--4 publisher -->
|
||||
<b-row v-else-if="gdtEntryType === 4">
|
||||
<div class="col-6 text-right">
|
||||
<div>{{ $t('gdt.your-share') }}</div>
|
||||
<div>{{ $t('gdt.credit') }}</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div>5%</div>
|
||||
<div>{{ $n(amount, 'decimal') }} GDT</div>
|
||||
</div>
|
||||
</b-row>
|
||||
<!-- 1, 2, 3, 5, 6 spenden in euro -->
|
||||
<b-row v-else>
|
||||
<div class="col-6 text-right">
|
||||
<div>{{ $t('gdt.contribution') }}</div>
|
||||
<div>{{ $t('gdt.credit') }}</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div>{{ $n(amount, 'decimal') }} €</div>
|
||||
<div>{{ $n(gdt, 'decimal') }} GDT</div>
|
||||
</div>
|
||||
</b-row>
|
||||
|
||||
<!-- Betrag ENDE-->
|
||||
|
||||
<!-- Nachricht-->
|
||||
<b-row v-if="comment && gdtEntryType !== 7">
|
||||
<div class="col-6 text-right">
|
||||
{{ $t('form.memo') }}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
{{ comment }}
|
||||
</div>
|
||||
</b-row>
|
||||
|
||||
<!-- Datum-->
|
||||
<b-row v-if="date" class="gdt-list-row text-header">
|
||||
<div class="col-6 text-right">
|
||||
{{ $t('form.date') }}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
{{ $d($moment(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
|
||||
</div>
|
||||
</b-row>
|
||||
</div>
|
||||
|
||||
<!-- Collaps START -->
|
||||
|
||||
<b-collapse v-if="gdtEntryType" :id="'a' + date + ''" class="pb-4">
|
||||
<div style="border: 0px; background-color: #f1f1f1" class="p-2 pb-4 mb-4">
|
||||
<!-- Überschrift -->
|
||||
<b-row class="gdt-list-clooaps-header-text text-center pb-3">
|
||||
<div class="col h4" v-if="gdtEntryType === 7">
|
||||
{{ $t('gdt.conversion-gdt-euro') }}
|
||||
</div>
|
||||
<div class="col h4" v-else-if="gdtEntryType === 4">
|
||||
{{ $t('gdt.publisher') }}
|
||||
</div>
|
||||
<div class="col h4" v-else>{{ $t('gdt.calculation') }}</div>
|
||||
</b-row>
|
||||
|
||||
<!-- 7 nur GDT erhalten -->
|
||||
<b-row class="gdt-list-clooaps-box-7" v-if="gdtEntryType == 7">
|
||||
<div class="col-6 text-right clooaps-col-left">
|
||||
<div>{{ $t('gdt.raise') }}</div>
|
||||
<div>{{ $t('gdt.conversion') }}</div>
|
||||
</div>
|
||||
<div class="col-6 clooaps-col-right">
|
||||
<div>{{ factor * 100 }} %</div>
|
||||
<div>
|
||||
{{ $n(amount, 'decimal') }} GDT * {{ factor * 100 }} % =
|
||||
{{ $n(gdt, 'decimal') }} GDT
|
||||
</div>
|
||||
</div>
|
||||
</b-row>
|
||||
<!-- 4 publisher -->
|
||||
<b-row class="gdt-list-clooaps-box-4" v-else-if="gdtEntryType === 4">
|
||||
<div class="col-6 text-right clooaps-col-left"></div>
|
||||
<div class="col-6 clooaps-col-right"></div>
|
||||
</b-row>
|
||||
|
||||
<!-- 1, 2, 3, 5, 6 spenden in euro -->
|
||||
<b-row class="gdt-list-clooaps-box--all" v-else>
|
||||
<div class="col-6 text-right clooaps-col-left">
|
||||
<div>{{ $t('gdt.factor') }}</div>
|
||||
<div>{{ $t('gdt.formula') }}</div>
|
||||
</div>
|
||||
<div class="col-6 clooaps-col-right">
|
||||
<div>{{ factor }} GDT pro €</div>
|
||||
<div>
|
||||
{{ $n(amount, 'decimal') }} € * {{ factor }} GDT / € =
|
||||
{{ $n(gdt, 'decimal') }} GDT
|
||||
</div>
|
||||
</div>
|
||||
</b-row>
|
||||
</div>
|
||||
</b-collapse>
|
||||
<!-- Collaps ENDE -->
|
||||
<transaction
|
||||
:amount="amount"
|
||||
:date="date"
|
||||
:comment="comment"
|
||||
:gdtEntryType="gdtEntryType"
|
||||
:factor="factor"
|
||||
:gdt="gdt"
|
||||
></transaction>
|
||||
</div>
|
||||
</div>
|
||||
<pagination-buttons
|
||||
@ -162,26 +42,13 @@
|
||||
<script>
|
||||
import { listGDTEntriesQuery } from '../../../graphql/queries'
|
||||
import PaginationButtons from '../../../components/PaginationButtons'
|
||||
|
||||
function iconByType(typeId) {
|
||||
switch (typeId) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 5:
|
||||
case 6:
|
||||
return { icon: 'heart', classes: 'gradido-global-color-accent' }
|
||||
case 4:
|
||||
return { icon: 'person-check', classes: 'gradido-global-color-accent' }
|
||||
case 7:
|
||||
return { icon: 'gift', classes: 'gradido-global-color-accent' }
|
||||
}
|
||||
}
|
||||
import Transaction from '../../../components/Transaction.vue'
|
||||
|
||||
export default {
|
||||
name: 'gdt-transaction-list',
|
||||
components: {
|
||||
PaginationButtons,
|
||||
Transaction,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -223,18 +90,6 @@ export default {
|
||||
this.$toasted.error(error.message)
|
||||
})
|
||||
},
|
||||
getIcon(givenType) {
|
||||
const type = iconByType(givenType)
|
||||
if (type)
|
||||
return {
|
||||
icon: type.icon,
|
||||
class: type.classes + ' m-mb-1 font2em',
|
||||
}
|
||||
this.throwError('no icon to given type: ' + givenType)
|
||||
},
|
||||
throwError(msg) {
|
||||
throw new Error(msg)
|
||||
},
|
||||
showNext() {
|
||||
this.currentPage++
|
||||
this.updateGdt()
|
||||
|
||||
@ -27,6 +27,11 @@ describe('Register', () => {
|
||||
$apollo: {
|
||||
query: resgisterUserQueryMock,
|
||||
},
|
||||
$store: {
|
||||
state: {
|
||||
language: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
@ -43,7 +48,7 @@ describe('Register', () => {
|
||||
})
|
||||
|
||||
it('renders the Register form', () => {
|
||||
expect(wrapper.find('div.register-form').exists()).toBeTruthy()
|
||||
expect(wrapper.find('div#registerform').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('Register header', () => {
|
||||
@ -86,11 +91,11 @@ describe('Register', () => {
|
||||
expect(wrapper.find('input[name="form.passwordRepeat"]').exists()).toBeTruthy()
|
||||
})
|
||||
it('has Language selected field', () => {
|
||||
expect(wrapper.find('#selectedLanguage').exists()).toBeTruthy()
|
||||
expect(wrapper.find('.selectedLanguage').exists()).toBeTruthy()
|
||||
})
|
||||
it('selected Language value de', async () => {
|
||||
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
|
||||
expect(wrapper.find('#selectedLanguage').element.value).toBe('de')
|
||||
wrapper.find('.selectedLanguage').findAll('option').at(1).setSelected()
|
||||
expect(wrapper.find('.selectedLanguage').element.value).toBe('de')
|
||||
})
|
||||
|
||||
it('has 1 checkbox input fields', () => {
|
||||
@ -133,14 +138,14 @@ describe('Register', () => {
|
||||
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
|
||||
wrapper.find('input[name="form.password"]').setValue('Aa123456')
|
||||
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
|
||||
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
|
||||
wrapper.find('.language-switch-select').findAll('option').at(1).setSelected()
|
||||
wrapper.find('input[name="site.signup.agree"]').setChecked(true)
|
||||
})
|
||||
|
||||
it('reset selected value language', async () => {
|
||||
await wrapper.find('button.ml-2').trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.find('#selectedLanguage').element.value).toBe('')
|
||||
expect(wrapper.find('.language-switch-select').element.value).toBe(undefined)
|
||||
})
|
||||
|
||||
it('resets the firstName field after clicking the reset button', async () => {
|
||||
@ -187,7 +192,7 @@ describe('Register', () => {
|
||||
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
|
||||
wrapper.find('input[name="form.password"]').setValue('Aa123456')
|
||||
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
|
||||
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
|
||||
wrapper.find('.language-switch-select').findAll('option').at(1).setSelected()
|
||||
})
|
||||
|
||||
describe('server sends back error', () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="register-form">
|
||||
<div id="registerform">
|
||||
<!-- Header -->
|
||||
<div class="header p-4">
|
||||
<b-container class="container">
|
||||
@ -87,12 +87,7 @@
|
||||
<b-row>
|
||||
<b-col cols="12">
|
||||
{{ $t('language') }}
|
||||
<b-form-select
|
||||
id="selectedLanguage"
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
class="mb-3"
|
||||
></b-form-select>
|
||||
<language-switch-select @update-language="updateLanguage" />
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
@ -145,10 +140,11 @@
|
||||
<script>
|
||||
import InputEmail from '../../components/Inputs/InputEmail.vue'
|
||||
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation.vue'
|
||||
import LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue'
|
||||
import { resgisterUserQuery } from '../../graphql/queries'
|
||||
|
||||
export default {
|
||||
components: { InputPasswordConfirmation, InputEmail },
|
||||
components: { InputPasswordConfirmation, InputEmail, LanguageSwitchSelect },
|
||||
name: 'register',
|
||||
data() {
|
||||
return {
|
||||
@ -162,12 +158,7 @@ export default {
|
||||
passwordRepeat: '',
|
||||
},
|
||||
},
|
||||
selected: null,
|
||||
options: [
|
||||
{ value: null, text: this.$t('select_language') },
|
||||
{ value: 'de', text: this.$t('languages.de') },
|
||||
{ value: 'en', text: this.$t('languages.en') },
|
||||
],
|
||||
language: '',
|
||||
submitted: false,
|
||||
showError: false,
|
||||
messageError: '',
|
||||
@ -175,6 +166,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateLanguage(e) {
|
||||
this.language = e
|
||||
},
|
||||
getValidationState({ dirty, validated, valid = null }) {
|
||||
return dirty || validated ? valid : null
|
||||
},
|
||||
@ -189,7 +183,7 @@ export default {
|
||||
},
|
||||
agree: false,
|
||||
}
|
||||
this.selected = null
|
||||
this.language = ''
|
||||
this.$nextTick(() => {
|
||||
this.$refs.observer.reset()
|
||||
})
|
||||
@ -203,7 +197,7 @@ export default {
|
||||
firstName: this.form.firstname,
|
||||
lastName: this.form.lastname,
|
||||
password: this.form.password.password,
|
||||
language: this.selected,
|
||||
language: this.language,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
@ -212,7 +206,7 @@ export default {
|
||||
this.form.lastname = ''
|
||||
this.form.password.password = ''
|
||||
this.form.password.passwordRepeat = ''
|
||||
this.selected = null
|
||||
this.language = ''
|
||||
this.$router.push('/thx/register')
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -228,7 +222,7 @@ export default {
|
||||
this.form.lastname = ''
|
||||
this.form.password.password = ''
|
||||
this.form.password.passwordRepeat = ''
|
||||
this.selected = null
|
||||
this.language = ''
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
@ -244,7 +238,7 @@ export default {
|
||||
return this.form.email !== ''
|
||||
},
|
||||
languageFilled() {
|
||||
return this.selected !== null
|
||||
return this.language !== null && this.language !== ''
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,81 +1,71 @@
|
||||
<template>
|
||||
<div class="userdata_form" fluid="sm">
|
||||
<div
|
||||
id="userdata_form"
|
||||
class="bg-transparent pt-3 pb-3"
|
||||
style="background-color: #ebebeba3 !important"
|
||||
>
|
||||
<b-container>
|
||||
<b-row class="text-right">
|
||||
<b-col class="mb-3">
|
||||
<a @click="showUserData ? (showUserData = !showUserData) : cancelEdit()">
|
||||
<span class="pointer mr-3">{{ $t('form.change-name') }}</span>
|
||||
<b-card id="userdata_form" class="bg-transparent" style="background-color: #ebebeba3 !important">
|
||||
<div>
|
||||
<b-row class="mb-4 text-right">
|
||||
<b-col class="text-right">
|
||||
<a @click="showUserData ? (showUserData = !showUserData) : cancelEdit()">
|
||||
<span class="pointer mr-3">{{ $t('form.change-name') }}</span>
|
||||
<b-icon v-if="showUserData" class="pointer ml-3" icon="pencil"></b-icon>
|
||||
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
|
||||
</a>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
|
||||
<b-icon v-if="showUserData" class="pointer" icon="pencil">
|
||||
{{ $t('form.change') }}
|
||||
</b-icon>
|
||||
|
||||
<b-icon v-else class="pointer" icon="x-circle" variant="danger"></b-icon>
|
||||
</a>
|
||||
<b-container>
|
||||
<b-form @keyup.prevent="loadSubmitButton">
|
||||
<b-row class="mb-3">
|
||||
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
|
||||
<small>{{ $t('form.firstname') }}</small>
|
||||
</b-col>
|
||||
<b-col v-if="showUserData" class="h2 col-sm-10 col-md-9">
|
||||
{{ form.firstName }}
|
||||
</b-col>
|
||||
<b-col v-else class="col-md-9 col-sm-10">
|
||||
<b-input type="text" v-model="form.firstName"></b-input>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="mb-3">
|
||||
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
|
||||
<small>{{ $t('form.lastname') }}</small>
|
||||
</b-col>
|
||||
<b-col v-if="showUserData" class="h2 col-sm-10 col-md-9">
|
||||
{{ form.lastName }}
|
||||
</b-col>
|
||||
<b-col v-else class="col-md-9 col-sm-10">
|
||||
<b-input type="text" v-model="form.lastName"></b-input>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="mb-3" v-show="false">
|
||||
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
|
||||
<small>{{ $t('form.description') }}</small>
|
||||
</b-col>
|
||||
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
|
||||
{{ form.description }}
|
||||
</b-col>
|
||||
<b-col v-else class="col-sm-10 col-md-9">
|
||||
<b-textarea rows="3" max-rows="6" v-model="form.description"></b-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
|
||||
<b-container>
|
||||
<b-form @keyup.prevent="loadSubmitButton">
|
||||
<b-row class="mb-3">
|
||||
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
|
||||
<small>{{ $t('form.firstname') }}</small>
|
||||
</b-col>
|
||||
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
|
||||
{{ form.firstName }}
|
||||
</b-col>
|
||||
<b-col v-else class="col-md-9 col-sm-10">
|
||||
<b-input type="text" v-model="form.firstName"></b-input>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="mb-3">
|
||||
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
|
||||
<small>{{ $t('form.lastname') }}</small>
|
||||
</b-col>
|
||||
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
|
||||
{{ form.lastName }}
|
||||
</b-col>
|
||||
<b-col v-else class="col-md-9 col-sm-10">
|
||||
<b-input type="text" v-model="form.lastName"></b-input>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="mb-3" v-show="false">
|
||||
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
|
||||
<small>{{ $t('form.description') }}</small>
|
||||
</b-col>
|
||||
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
|
||||
{{ form.description }}
|
||||
</b-col>
|
||||
<b-col v-else class="col-sm-10 col-md-9">
|
||||
<b-textarea rows="3" max-rows="6" v-model="form.description"></b-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<b-row class="text-right" v-if="!showUserData">
|
||||
<b-col>
|
||||
<div class="text-right" ref="submitButton">
|
||||
<b-button
|
||||
:variant="loading ? 'default' : 'success'"
|
||||
@click="onSubmit"
|
||||
type="submit"
|
||||
class="mt-4"
|
||||
:disabled="loading"
|
||||
>
|
||||
{{ $t('form.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</b-container>
|
||||
</div>
|
||||
</div>
|
||||
<b-row class="text-right" v-if="!showUserData">
|
||||
<b-col>
|
||||
<div class="text-right" ref="submitButton">
|
||||
<b-button
|
||||
:variant="loading ? 'default' : 'success'"
|
||||
@click="onSubmit"
|
||||
type="submit"
|
||||
class="mt-4"
|
||||
:disabled="loading"
|
||||
>
|
||||
{{ $t('form.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</b-container>
|
||||
</b-card>
|
||||
</template>
|
||||
<script>
|
||||
import { updateUserInfos } from '../../../graphql/queries'
|
||||
|
||||
@ -1,48 +1,43 @@
|
||||
<template>
|
||||
<div
|
||||
id="change_pwd"
|
||||
class="bg-transparent pt-3 pb-3"
|
||||
style="background-color: #ebebeba3 !important"
|
||||
>
|
||||
<b-container>
|
||||
<div>
|
||||
<b-row class="mb-4 text-right">
|
||||
<b-col class="text-right">
|
||||
<a @click="!editPassword ? (editPassword = !editPassword) : cancelEdit()">
|
||||
<span class="pointer mr-3">{{ $t('form.change-password') }}</span>
|
||||
<b-icon v-if="!editPassword" class="pointer ml-3" icon="pencil" />
|
||||
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
|
||||
</a>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
<div v-if="editPassword">
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
|
||||
<b-row class="mb-2">
|
||||
<b-col>
|
||||
<input-password
|
||||
:label="$t('form.password_old')"
|
||||
:placeholder="$t('form.password_old')"
|
||||
v-model="form.password"
|
||||
></input-password>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<input-password-confirmation v-model="form.newPassword" :register="register" />
|
||||
<b-row class="text-right">
|
||||
<b-col>
|
||||
<div class="text-right">
|
||||
<b-button type="submit" variant="primary" class="mt-4">
|
||||
{{ $t('form.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</validation-observer>
|
||||
</div>
|
||||
</b-container>
|
||||
</div>
|
||||
<b-card id="change_pwd" class="bg-transparent" style="background-color: #ebebeba3 !important">
|
||||
<div>
|
||||
<b-row class="mb-4 text-right">
|
||||
<b-col class="text-right">
|
||||
<a @click="showPassword ? (showPassword = !showPassword) : cancelEdit()">
|
||||
<span class="pointer mr-3">{{ $t('form.change-password') }}</span>
|
||||
<b-icon v-if="showPassword" class="pointer ml-3" icon="pencil"></b-icon>
|
||||
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
|
||||
</a>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
|
||||
<div v-if="!showPassword">
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
|
||||
<b-row class="mb-2">
|
||||
<b-col>
|
||||
<input-password
|
||||
:label="$t('form.password_old')"
|
||||
:placeholder="$t('form.password_old')"
|
||||
v-model="form.password"
|
||||
></input-password>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<input-password-confirmation v-model="form.newPassword" :register="register" />
|
||||
<b-row class="text-right">
|
||||
<b-col>
|
||||
<div class="text-right">
|
||||
<b-button type="submit" variant="primary" class="mt-4">
|
||||
{{ $t('form.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</validation-observer>
|
||||
</div>
|
||||
</b-card>
|
||||
</template>
|
||||
<script>
|
||||
import InputPassword from '../../../components/Inputs/InputPassword'
|
||||
@ -57,7 +52,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editPassword: false,
|
||||
showPassword: true,
|
||||
email: null,
|
||||
form: {
|
||||
password: '',
|
||||
@ -71,7 +66,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
cancelEdit() {
|
||||
this.editPassword = false
|
||||
this.showPassword = true
|
||||
this.form.password = ''
|
||||
this.form.passwordNew = ''
|
||||
this.form.passwordNewRepeat = ''
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserCardLanguage from './UserCard_Language'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockAPIcall = jest.fn()
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const toastSuccessMock = jest.fn()
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
describe('UserCard_Language', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
language: 'de',
|
||||
},
|
||||
commit: storeCommitMock,
|
||||
},
|
||||
$toasted: {
|
||||
success: toastSuccessMock,
|
||||
error: toastErrorMock,
|
||||
},
|
||||
$apollo: {
|
||||
query: mockAPIcall,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(UserCardLanguage, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div#formuserlanguage').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has an edit icon', () => {
|
||||
expect(wrapper.find('svg.bi-pencil').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
91
frontend/src/views/Pages/UserProfile/UserCard_Language.vue
Normal file
91
frontend/src/views/Pages/UserProfile/UserCard_Language.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<b-card
|
||||
id="formuserlanguage"
|
||||
class="bg-transparent"
|
||||
style="background-color: #ebebeba3 !important"
|
||||
>
|
||||
<div>
|
||||
<b-row class="mb-4 text-right">
|
||||
<b-col class="text-right">
|
||||
<a @click="showLanguage ? (showLanguage = !showLanguage) : cancelEdit()">
|
||||
<span class="pointer mr-3">{{ $t('form.changeLanguage') }}</span>
|
||||
<b-icon v-if="showLanguage" class="pointer ml-3" icon="pencil"></b-icon>
|
||||
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
|
||||
</a>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
|
||||
<div v-if="showLanguage">
|
||||
<b-row class="mb-3">
|
||||
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
|
||||
<small>{{ $t('language') }}</small>
|
||||
</b-col>
|
||||
<b-col class="h2 col-md-9 col-sm-10">{{ $store.state.language }}</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div>
|
||||
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
|
||||
<b-row class="mb-2">
|
||||
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
|
||||
<small>{{ $t('language') }}</small>
|
||||
</b-col>
|
||||
<b-col class="col-md-9 col-sm-10">
|
||||
<language-switch-select @update-language="updateLanguage" :language="language" />
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<b-row class="text-right">
|
||||
<b-col>
|
||||
<div class="text-right">
|
||||
<b-button type="submit" variant="primary" class="mt-4">
|
||||
{{ $t('form.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</div>
|
||||
</div>
|
||||
</b-card>
|
||||
</template>
|
||||
<script>
|
||||
import LanguageSwitchSelect from '../../../components/LanguageSwitchSelect.vue'
|
||||
import { updateUserInfos } from '../../../graphql/queries'
|
||||
|
||||
export default {
|
||||
name: 'FormUserLanguage',
|
||||
components: { LanguageSwitchSelect },
|
||||
data() {
|
||||
return {
|
||||
showLanguage: true,
|
||||
language: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateLanguage(e) {
|
||||
this.language = e
|
||||
},
|
||||
cancelEdit() {
|
||||
this.showLanguage = true
|
||||
},
|
||||
async onSubmit() {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: updateUserInfos,
|
||||
variables: {
|
||||
language: this.$store.state.language,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.cancelEdit()
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.error(error.message)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -23,16 +23,16 @@ describe('UserProfileOverview', () => {
|
||||
expect(wrapper.findComponent({ name: 'UserCard' }).exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has a user data form', () => {
|
||||
it('has a user first and last name form', () => {
|
||||
expect(wrapper.findComponent({ name: 'FormUserData' }).exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
// it('has a user name form', () => {
|
||||
// expect(wrapper.findComponent({ name: 'FormUsername' }).exists()).toBeTruthy()
|
||||
// })
|
||||
|
||||
it('has a user password form', () => {
|
||||
it('has a user change password form', () => {
|
||||
expect(wrapper.findComponent({ name: 'FormUserPasswort' }).exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has a user change language form', () => {
|
||||
expect(wrapper.findComponent({ name: 'FormUserLanguage' }).exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,24 +1,25 @@
|
||||
<template>
|
||||
<div fluid="sm">
|
||||
<user-card :balance="balance" :transactionCount="transactionCount"></user-card>
|
||||
<p><form-user-data /></p>
|
||||
<!--<form-username />-->
|
||||
<form-user-data />
|
||||
<hr />
|
||||
<p><form-user-passwort /></p>
|
||||
<form-user-passwort />
|
||||
<hr />
|
||||
<form-user-language />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import UserCard from './UserProfile/UserCard.vue'
|
||||
import FormUserData from './UserProfile/UserCard_FormUserData.vue'
|
||||
// import FormUsername from './UserProfile/UserCard_FormUsername.vue'
|
||||
import FormUserPasswort from './UserProfile/UserCard_FormUserPasswort.vue'
|
||||
import FormUserLanguage from './UserProfile/UserCard_Language.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserCard,
|
||||
FormUserData,
|
||||
// FormUsername,
|
||||
FormUserPasswort,
|
||||
FormUserLanguage,
|
||||
},
|
||||
props: {
|
||||
balance: { type: Number, default: 0 },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user