mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into 2473-feature-emails-further-design-of-html-emails
This commit is contained in:
commit
6796c1a6b9
@ -13,7 +13,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import { de, en, fr, es, nl } from 'date-fns/locale'
|
||||
import { de, enUS as en, fr, es, nl } from 'date-fns/locale'
|
||||
|
||||
const locales = { en, de, es, fr, nl }
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
"graphql": "^15.5.1",
|
||||
"graphql-request": "5.0.0",
|
||||
"i18n": "^0.15.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jose": "^4.14.4",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"log4js": "^6.4.6",
|
||||
"mysql2": "^2.3.0",
|
||||
@ -52,7 +52,6 @@
|
||||
"@types/faker": "^5.5.9",
|
||||
"@types/i18n": "^0.13.4",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/jsonwebtoken": "^8.5.2",
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"@types/node": "^16.10.3",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { JwtPayload } from 'jsonwebtoken'
|
||||
import { JWTPayload } from 'jose'
|
||||
|
||||
export interface CustomJwtPayload extends JwtPayload {
|
||||
export interface CustomJwtPayload extends JWTPayload {
|
||||
gradidoID: string
|
||||
}
|
||||
|
||||
@ -8,4 +8,5 @@ export const INALIENABLE_RIGHTS = [
|
||||
RIGHTS.SET_PASSWORD,
|
||||
RIGHTS.QUERY_TRANSACTION_LINK,
|
||||
RIGHTS.QUERY_OPT_IN,
|
||||
RIGHTS.CHECK_USERNAME,
|
||||
]
|
||||
|
||||
@ -1,22 +1,33 @@
|
||||
import { verify, sign } from 'jsonwebtoken'
|
||||
import { SignJWT, jwtVerify } from 'jose'
|
||||
|
||||
import { CONFIG } from '@/config/'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
import { CustomJwtPayload } from './CustomJwtPayload'
|
||||
|
||||
export const decode = (token: string): CustomJwtPayload | null => {
|
||||
export const decode = async (token: string): Promise<CustomJwtPayload | null> => {
|
||||
if (!token) throw new LogError('401 Unauthorized')
|
||||
|
||||
try {
|
||||
return verify(token, CONFIG.JWT_SECRET) as CustomJwtPayload
|
||||
const secret = new TextEncoder().encode(CONFIG.JWT_SECRET)
|
||||
const { payload } = await jwtVerify(token, secret, {
|
||||
issuer: 'urn:gradido:issuer',
|
||||
audience: 'urn:gradido:audience',
|
||||
})
|
||||
return payload as CustomJwtPayload
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const encode = (gradidoID: string): string => {
|
||||
const token = sign({ gradidoID }, CONFIG.JWT_SECRET, {
|
||||
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||
})
|
||||
export const encode = async (gradidoID: string): Promise<string> => {
|
||||
const secret = new TextEncoder().encode(CONFIG.JWT_SECRET)
|
||||
const token = await new SignJWT({ gradidoID, 'urn:gradido:claim': true })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setIssuer('urn:gradido:issuer')
|
||||
.setAudience('urn:gradido:audience')
|
||||
.setExpirationTime(CONFIG.JWT_EXPIRES_IN)
|
||||
.sign(secret)
|
||||
return token
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ export enum RIGHTS {
|
||||
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||
OPEN_CREATIONS = 'OPEN_CREATIONS',
|
||||
USER = 'USER',
|
||||
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||
// Admin
|
||||
SEARCH_USERS = 'SEARCH_USERS',
|
||||
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||
|
||||
@ -21,7 +21,7 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
|
||||
}
|
||||
|
||||
// Decode the token
|
||||
const decoded = decode(context.token)
|
||||
const decoded = await decode(context.token)
|
||||
if (!decoded) {
|
||||
throw new LogError('403.13 - Client certificate revoked')
|
||||
}
|
||||
@ -49,6 +49,6 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
|
||||
}
|
||||
|
||||
// set new header token
|
||||
context.setHeaders.push({ key: 'token', value: encode(decoded.gradidoID) })
|
||||
context.setHeaders.push({ key: 'token', value: await encode(decoded.gradidoID) })
|
||||
return true
|
||||
}
|
||||
|
||||
@ -53,6 +53,7 @@ import {
|
||||
searchAdminUsers,
|
||||
searchUsers,
|
||||
user as userQuery,
|
||||
checkUsername,
|
||||
} from '@/seeds/graphql/queries'
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
|
||||
@ -2442,6 +2443,34 @@ describe('UserResolver', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('check username', () => {
|
||||
describe('reserved alias', () => {
|
||||
it('returns false', async () => {
|
||||
await expect(
|
||||
query({ query: checkUsername, variables: { username: 'root' } }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
checkUsername: false,
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid alias', () => {
|
||||
it('returns true', async () => {
|
||||
await expect(
|
||||
query({ query: checkUsername, variables: { username: 'valid' } }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
checkUsername: true,
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('printTimeDuration', () => {
|
||||
|
||||
@ -186,7 +186,7 @@ export class UserResolver {
|
||||
|
||||
context.setHeaders.push({
|
||||
key: 'token',
|
||||
value: encode(dbUser.gradidoID),
|
||||
value: await encode(dbUser.gradidoID),
|
||||
})
|
||||
|
||||
await EVENT_USER_LOGIN(dbUser)
|
||||
@ -498,6 +498,17 @@ export class UserResolver {
|
||||
return true
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.CHECK_USERNAME])
|
||||
@Query(() => Boolean)
|
||||
async checkUsername(@Arg('username') username: string): Promise<boolean> {
|
||||
try {
|
||||
await validateAlias(username)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.UPDATE_USER_INFOS])
|
||||
@Mutation(() => Boolean)
|
||||
async updateUserInfos(
|
||||
|
||||
@ -22,6 +22,12 @@ export const queryOptIn = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const checkUsername = gql`
|
||||
query ($username: String!) {
|
||||
checkUsername(username: $username)
|
||||
}
|
||||
`
|
||||
|
||||
export const transactionsQuery = gql`
|
||||
query ($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
||||
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
||||
|
||||
@ -1059,13 +1059,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/jsonwebtoken@^8.5.2":
|
||||
version "8.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.5.tgz#da5f2f4baee88f052ef3e4db4c1a0afb46cff22c"
|
||||
integrity sha512-OGqtHQ7N5/Ap/TUwO6IgHDuLiAoTmHhGpNvgkCm/F4N6pKzx/RBSfr2OXZSwC6vkfnsEdb6+7DNZVtiXiwdwFw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/keygrip@*":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
|
||||
@ -2002,11 +1995,6 @@ bser@2.1.1:
|
||||
dependencies:
|
||||
node-int64 "^0.4.0"
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
@ -2699,13 +2687,6 @@ duplexer3@^0.1.4:
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
@ -4805,6 +4786,11 @@ jest@^27.2.4:
|
||||
import-local "^3.0.2"
|
||||
jest-cli "^27.2.5"
|
||||
|
||||
jose@^4.14.4:
|
||||
version "4.14.4"
|
||||
resolved "https://registry.yarnpkg.com/jose/-/jose-4.14.4.tgz#59e09204e2670c3164ee24cbfe7115c6f8bff9ca"
|
||||
integrity sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==
|
||||
|
||||
js-sdsl@^4.1.4:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711"
|
||||
@ -4918,22 +4904,6 @@ jsonfile@^6.0.1:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonwebtoken@^8.5.1:
|
||||
version "8.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
|
||||
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash.includes "^4.3.0"
|
||||
lodash.isboolean "^3.0.3"
|
||||
lodash.isinteger "^4.0.4"
|
||||
lodash.isnumber "^3.0.3"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.once "^4.0.0"
|
||||
ms "^2.1.1"
|
||||
semver "^5.6.0"
|
||||
|
||||
jstransformer@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3"
|
||||
@ -4953,23 +4923,6 @@ juice@^8.0.0:
|
||||
slick "^1.12.2"
|
||||
web-resource-inliner "^6.0.1"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
keyv@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
|
||||
@ -5073,46 +5026,11 @@ lodash.get@^4.4.2:
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
|
||||
|
||||
lodash.isboolean@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
|
||||
|
||||
lodash.isinteger@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||
|
||||
lodash.sortby@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
@ -6344,7 +6262,7 @@ semver@7.x, semver@^7.3.2, semver@^7.3.4:
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
|
||||
semver@^5.5.0, semver@^5.7.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
||||
@ -3,15 +3,23 @@
|
||||
<div class="bg-white appBoxShadow gradido-border-radius p-3">
|
||||
<div class="h3 mb-4">{{ $t('form.send_check') }}</div>
|
||||
<b-row class="mt-5">
|
||||
<b-col cols="2"></b-col>
|
||||
<b-col>
|
||||
<div class="h4">{{ userName ? userName : identifier }}</div>
|
||||
<div class="mt-3 h5">{{ $t('form.memo') }}</div>
|
||||
<div>{{ memo }}</div>
|
||||
</b-col>
|
||||
<b-col cols="3">
|
||||
<div class="small">{{ $t('send_gdd') }}</div>
|
||||
<div>{{ amount | GDD }}</div>
|
||||
<b-col cols="12">
|
||||
<b-row class="mt-3">
|
||||
<b-col class="h5">{{ $t('form.recipientCommunity') }}</b-col>
|
||||
<b-col>{{ communityName }}</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col class="h5">{{ $t('form.recipient') }}</b-col>
|
||||
<b-col>{{ userName ? userName : identifier }}</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col class="h5">{{ $t('form.amount') }}</b-col>
|
||||
<b-col>{{ amount | GDD }}</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col class="h5">{{ $t('form.memo') }}</b-col>
|
||||
<b-col>{{ memo }}</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
@ -58,6 +66,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { COMMUNITY_NAME } from '@/config'
|
||||
|
||||
export default {
|
||||
name: 'TransactionConfirmationSend',
|
||||
props: {
|
||||
@ -70,6 +80,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
disabled: false,
|
||||
communityName: COMMUNITY_NAME,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@ -49,6 +49,14 @@
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-row>
|
||||
<b-col class="mb-4" cols="12" v-if="radioSelected === sendTypes.send">
|
||||
<b-row>
|
||||
<b-col>{{ $t('form.recipientCommunity') }}</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col class="font-weight-bold">{{ communityName }}</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="12" v-if="radioSelected === sendTypes.send">
|
||||
<div v-if="!gradidoID">
|
||||
<input-email
|
||||
@ -131,6 +139,7 @@ import InputAmount from '@/components/Inputs/InputAmount'
|
||||
import InputTextarea from '@/components/Inputs/InputTextarea'
|
||||
import { user as userQuery } from '@/graphql/queries'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { COMMUNITY_NAME } from '@/config'
|
||||
|
||||
export default {
|
||||
name: 'TransactionForm',
|
||||
@ -155,6 +164,7 @@ export default {
|
||||
},
|
||||
radioSelected: this.selected,
|
||||
userName: '',
|
||||
communityName: COMMUNITY_NAME,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
containsLowercaseCharacter: true,
|
||||
containsUppercaseCharacter: true,
|
||||
containsNumericCharacter: true,
|
||||
atLeastEightCharactera: true,
|
||||
atLeastEightCharacters: true,
|
||||
atLeastOneSpecialCharater: true,
|
||||
noWhitespaceCharacters: true,
|
||||
}"
|
||||
|
||||
71
frontend/src/components/Inputs/InputUsername.vue
Normal file
71
frontend/src/components/Inputs/InputUsername.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<validation-provider
|
||||
tag="div"
|
||||
:rules="rules"
|
||||
:name="name"
|
||||
:bails="!showAllErrors"
|
||||
:immediate="immediate"
|
||||
vid="username"
|
||||
v-slot="{ errors, valid, validated, ariaInput, ariaMsg }"
|
||||
>
|
||||
<b-form-group :label-for="labelFor">
|
||||
<b-form-input
|
||||
v-model="currentValue"
|
||||
v-bind="ariaInput"
|
||||
:id="labelFor"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
type="text"
|
||||
:state="validated ? valid : false"
|
||||
autocomplete="off"
|
||||
></b-form-input>
|
||||
<b-form-invalid-feedback v-bind="ariaMsg">
|
||||
<div v-if="showAllErrors">
|
||||
<span v-for="error in errors" :key="error">
|
||||
{{ error }}
|
||||
<br />
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ errors[0] }}
|
||||
</div>
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'InputUsername',
|
||||
props: {
|
||||
rules: {
|
||||
default: () => {
|
||||
return {
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
name: { type: String, default: 'username' },
|
||||
label: { type: String, default: 'Username' },
|
||||
placeholder: { type: String, default: 'Username' },
|
||||
value: { required: true, type: String },
|
||||
showAllErrors: { type: Boolean, default: false },
|
||||
immediate: { type: Boolean, default: false },
|
||||
unique: { type: Boolean, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.value,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labelFor() {
|
||||
return this.name + '-input-field'
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentValue() {
|
||||
this.$emit('input', this.currentValue)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -12,7 +12,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { formatDistance } from 'date-fns'
|
||||
import { en, de, es, fr, nl } from 'date-fns/locale'
|
||||
import { enUS as en, de, es, fr, nl } from 'date-fns/locale'
|
||||
|
||||
const locales = { en, de, es, fr, nl }
|
||||
|
||||
|
||||
157
frontend/src/components/UserSettings/UserName.spec.js
Normal file
157
frontend/src/components/UserSettings/UserName.spec.js
Normal file
@ -0,0 +1,157 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserName from './UserName'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
import { toastErrorSpy, toastSuccessSpy } from '@test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockAPIcall = jest.fn()
|
||||
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
describe('UserName Form', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
username: 'peter',
|
||||
},
|
||||
commit: storeCommitMock,
|
||||
},
|
||||
$apollo: {
|
||||
mutate: mockAPIcall,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(UserName, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div#username_form').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has an edit icon', () => {
|
||||
expect(wrapper.find('svg.bi-pencil').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the username', () => {
|
||||
expect(wrapper.findAll('div.col').at(2).text()).toBe('peter')
|
||||
})
|
||||
|
||||
describe('edit username', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.find('svg.bi-pencil').trigger('click')
|
||||
})
|
||||
|
||||
it('shows an cancel icon', () => {
|
||||
expect(wrapper.find('svg.bi-x-circle').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('closes the input when cancel icon is clicked', async () => {
|
||||
await wrapper.find('svg.bi-x-circle').trigger('click')
|
||||
expect(wrapper.find('input').exists()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('does not change the username when cancel is clicked', async () => {
|
||||
await wrapper.find('input').setValue('petra')
|
||||
await wrapper.find('svg.bi-x-circle').trigger('click')
|
||||
expect(wrapper.findAll('div.col').at(2).text()).toBe('peter')
|
||||
})
|
||||
|
||||
it('has a submit button', () => {
|
||||
expect(wrapper.find('button[type="submit"]').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('does not enable submit button when data is not changed', async () => {
|
||||
await wrapper.find('form').trigger('keyup')
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled')
|
||||
})
|
||||
|
||||
describe('successfull submit', () => {
|
||||
beforeEach(async () => {
|
||||
mockAPIcall.mockResolvedValue({
|
||||
data: {
|
||||
updateUserInfos: {
|
||||
validValues: 3,
|
||||
},
|
||||
},
|
||||
})
|
||||
jest.clearAllMocks()
|
||||
await wrapper.find('input').setValue('petra')
|
||||
await wrapper.find('form').trigger('keyup')
|
||||
await wrapper.find('button[type="submit"]').trigger('click')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('calls the API', () => {
|
||||
expect(mockAPIcall).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
alias: 'petra',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('commits username to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('username', 'petra')
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('settings.username.change-success')
|
||||
})
|
||||
|
||||
it('has an edit button again', () => {
|
||||
expect(wrapper.find('svg.bi-pencil').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('submit results in server error', () => {
|
||||
beforeEach(async () => {
|
||||
mockAPIcall.mockRejectedValue({
|
||||
message: 'Error',
|
||||
})
|
||||
jest.clearAllMocks()
|
||||
await wrapper.find('input').setValue('petra')
|
||||
await wrapper.find('form').trigger('keyup')
|
||||
await wrapper.find('button[type="submit"]').trigger('click')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('calls the API', () => {
|
||||
expect(mockAPIcall).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
alias: 'petra',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('no username in store', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$store.state.username = null
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('displays an information why to enter a username', () => {
|
||||
expect(wrapper.findAll('div.col').at(2).text()).toBe('settings.username.no-username')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
130
frontend/src/components/UserSettings/UserName.vue
Normal file
130
frontend/src/components/UserSettings/UserName.vue
Normal file
@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<b-card id="username_form" class="card-border-radius card-background-gray">
|
||||
<div>
|
||||
<b-row class="mb-4 text-right">
|
||||
<b-col class="text-right">
|
||||
<a
|
||||
class="cursor-pointer"
|
||||
@click="showUserData ? (showUserData = !showUserData) : cancelEdit()"
|
||||
>
|
||||
<span class="pointer mr-3">{{ $t('settings.username.change-username') }}</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>
|
||||
|
||||
<div>
|
||||
<validation-observer ref="usernameObserver" v-slot="{ handleSubmit, invalid }">
|
||||
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
|
||||
<b-row class="mb-3">
|
||||
<b-col class="col-12">
|
||||
<small>
|
||||
<b>{{ $t('form.username') }}</b>
|
||||
</small>
|
||||
</b-col>
|
||||
<b-col v-if="showUserData" class="col-12">
|
||||
<span v-if="username">
|
||||
{{ username }}
|
||||
</span>
|
||||
<div v-else class="alert">
|
||||
{{ $t('settings.username.no-username') }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col v-else class="col-12">
|
||||
<input-username
|
||||
v-model="username"
|
||||
:name="$t('form.username')"
|
||||
:placeholder="$t('form.username-placeholder')"
|
||||
:showAllErrors="true"
|
||||
:unique="true"
|
||||
:rules="rules"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="text-right" v-if="!showUserData">
|
||||
<b-col>
|
||||
<div class="text-right" ref="submitButton">
|
||||
<b-button
|
||||
:variant="disabled(invalid) ? 'light' : 'success'"
|
||||
@click="onSubmit"
|
||||
type="submit"
|
||||
:disabled="disabled(invalid)"
|
||||
>
|
||||
{{ $t('form.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</validation-observer>
|
||||
</div>
|
||||
</b-card>
|
||||
</template>
|
||||
<script>
|
||||
import { updateUserInfos } from '@/graphql/mutations'
|
||||
import InputUsername from '@/components/Inputs/InputUsername'
|
||||
|
||||
export default {
|
||||
name: 'UserName',
|
||||
components: {
|
||||
InputUsername,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showUserData: true,
|
||||
username: this.$store.state.username || '',
|
||||
usernameUnique: false,
|
||||
rules: {
|
||||
required: true,
|
||||
min: 3,
|
||||
max: 20,
|
||||
usernameAllowedChars: true,
|
||||
usernameHyphens: true,
|
||||
usernameUnique: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cancelEdit() {
|
||||
this.username = this.$store.state.username || ''
|
||||
this.showUserData = true
|
||||
},
|
||||
async onSubmit(event) {
|
||||
event.preventDefault()
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: updateUserInfos,
|
||||
variables: {
|
||||
alias: this.username,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.$store.commit('username', this.username)
|
||||
this.showUserData = true
|
||||
this.toastSuccess(this.$t('settings.username.change-success'))
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
disabled(invalid) {
|
||||
return !this.newUsername || invalid
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
newUsername() {
|
||||
return this.username !== this.$store.state.username
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
div.alert {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
@ -26,6 +26,7 @@ export const forgotPassword = gql`
|
||||
|
||||
export const updateUserInfos = gql`
|
||||
mutation(
|
||||
$alias: String
|
||||
$firstName: String
|
||||
$lastName: String
|
||||
$password: String
|
||||
@ -35,6 +36,7 @@ export const updateUserInfos = gql`
|
||||
$hideAmountGDT: Boolean
|
||||
) {
|
||||
updateUserInfos(
|
||||
alias: $alias
|
||||
firstName: $firstName
|
||||
lastName: $lastName
|
||||
password: $password
|
||||
@ -145,6 +147,7 @@ export const login = gql`
|
||||
mutation($email: String!, $password: String!, $publisherId: Int) {
|
||||
login(email: $email, password: $password, publisherId: $publisherId) {
|
||||
gradidoID
|
||||
alias
|
||||
firstName
|
||||
lastName
|
||||
language
|
||||
|
||||
@ -89,6 +89,12 @@ export const queryOptIn = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const checkUsername = gql`
|
||||
query($username: String!) {
|
||||
checkUsername(username: $username)
|
||||
}
|
||||
`
|
||||
|
||||
export const queryTransactionLink = gql`
|
||||
query($code: String!) {
|
||||
queryTransactionLink(code: $code) {
|
||||
|
||||
@ -153,6 +153,7 @@
|
||||
"password_new_repeat": "Neues Passwort wiederholen",
|
||||
"password_old": "Altes Passwort",
|
||||
"recipient": "Empfänger",
|
||||
"recipientCommunity": "Gemeinschaft des Empfängers",
|
||||
"reply": "Antworten",
|
||||
"reset": "Zurücksetzen",
|
||||
"save": "Speichern",
|
||||
@ -166,12 +167,15 @@
|
||||
"thx": "Danke",
|
||||
"to": "bis",
|
||||
"to1": "an",
|
||||
"username": "Nutzername",
|
||||
"username-placeholder": "Gebe einen eindeutigen Nutzernamen ein",
|
||||
"validation": {
|
||||
"gddCreationTime": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens einer Nachkommastelle sein",
|
||||
"gddSendAmount": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein",
|
||||
"is-not": "Du kannst dir selbst keine Gradidos überweisen",
|
||||
"usernmae-regex": "Der Username muss mit einem Buchstaben beginnen, auf den mindestens zwei alpha-numerische Zeichen folgen müssen.",
|
||||
"usernmae-unique": "Der Username ist bereits vergeben."
|
||||
"username-allowed-chars": "Der Nutzername darf nur aus Buchstaben (ohne Umlaute), Zahlen, Binde- oder Unterstrichen bestehen.",
|
||||
"username-hyphens": "Binde- oder Unterstriche müssen zwischen Buchstaben oder Zahlen stehen.",
|
||||
"username-unique": "Der Nutzername ist bereits vergeben."
|
||||
},
|
||||
"your_amount": "Dein Betrag"
|
||||
},
|
||||
@ -319,7 +323,12 @@
|
||||
"subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen."
|
||||
},
|
||||
"showAmountGDD": "Dein GDD Betrag ist sichtbar.",
|
||||
"showAmountGDT": "Dein GDT Betrag ist sichtbar."
|
||||
"showAmountGDT": "Dein GDT Betrag ist sichtbar.",
|
||||
"username": {
|
||||
"change-success": "Dein Nutzername wurde erfolgreich geändert.",
|
||||
"change-username": "Nutzername ändern",
|
||||
"no-username": "Bitte gebe einen Nutzernamen ein. Damit hilfst du anderen Benutzern dich zu finden, ohne deine Email preisgeben zu müssen."
|
||||
}
|
||||
},
|
||||
"signin": "Anmelden",
|
||||
"signup": "Registrieren",
|
||||
|
||||
@ -153,6 +153,7 @@
|
||||
"password_new_repeat": "Repeat new password",
|
||||
"password_old": "Old password",
|
||||
"recipient": "Recipient",
|
||||
"recipientCommunity": "Community of the recipient",
|
||||
"reply": "Reply",
|
||||
"reset": "Reset",
|
||||
"save": "Save",
|
||||
@ -166,12 +167,15 @@
|
||||
"thx": "Thank you",
|
||||
"to": "to",
|
||||
"to1": "to",
|
||||
"username": "Username",
|
||||
"username-placeholder": "Enter a unique username",
|
||||
"validation": {
|
||||
"gddCreationTime": "The field {_field_} must be a number between {min} and {max} with at most one decimal place.",
|
||||
"gddSendAmount": "The {_field_} field must be a number between {min} and {max} with at most two digits after the decimal point",
|
||||
"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-unique": "This username is already taken."
|
||||
"username-allowed-chars": "The username may only contain letters, numbers, hyphens or underscores.",
|
||||
"username-hyphens": "Hyphens or underscores must be in between letters or numbers.",
|
||||
"username-unique": "This username is already taken."
|
||||
},
|
||||
"your_amount": "Your amount"
|
||||
},
|
||||
@ -319,7 +323,12 @@
|
||||
"subtitle": "If you have forgotten your password, you can reset it here."
|
||||
},
|
||||
"showAmountGDD": "Your GDD amount is visible.",
|
||||
"showAmountGDT": "Your GDT amount is visible."
|
||||
"showAmountGDT": "Your GDT amount is visible.",
|
||||
"username": {
|
||||
"change-success": "Your username has been changed successfully.",
|
||||
"change-username": "Change username",
|
||||
"no-username": "Please enter a username. This helps other users to find you without exposing your email."
|
||||
}
|
||||
},
|
||||
"signin": "Sign in",
|
||||
"signup": "Sign up",
|
||||
|
||||
@ -27,7 +27,7 @@ const filters = loadFilters(i18n)
|
||||
Vue.filter('amount', filters.amount)
|
||||
Vue.filter('GDD', filters.GDD)
|
||||
|
||||
loadAllRules(i18n)
|
||||
loadAllRules(i18n, apolloProvider.defaultClient)
|
||||
|
||||
addNavigationGuards(router, store, apolloProvider.defaultClient)
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
<user-card :balance="balance" :transactionCount="transactionCount"></user-card>
|
||||
<user-data />
|
||||
<hr />
|
||||
<user-name />
|
||||
<hr />
|
||||
<user-password />
|
||||
<hr />
|
||||
<user-language />
|
||||
@ -13,6 +15,7 @@
|
||||
<script>
|
||||
import UserCard from '@/components/UserSettings/UserCard'
|
||||
import UserData from '@/components/UserSettings/UserData'
|
||||
import UserName from '@/components/UserSettings/UserName'
|
||||
import UserPassword from '@/components/UserSettings/UserPassword'
|
||||
import UserLanguage from '@/components/UserSettings/UserLanguage'
|
||||
import UserNewsletter from '@/components/UserSettings/UserNewsletter'
|
||||
@ -22,6 +25,7 @@ export default {
|
||||
components: {
|
||||
UserCard,
|
||||
UserData,
|
||||
UserName,
|
||||
UserPassword,
|
||||
UserLanguage,
|
||||
UserNewsletter,
|
||||
|
||||
@ -16,9 +16,9 @@ export const mutations = {
|
||||
gradidoID: (state, gradidoID) => {
|
||||
state.gradidoID = gradidoID
|
||||
},
|
||||
// username: (state, username) => {
|
||||
// state.username = username
|
||||
// },
|
||||
username: (state, username) => {
|
||||
state.username = username
|
||||
},
|
||||
firstName: (state, firstName) => {
|
||||
state.firstName = firstName
|
||||
},
|
||||
@ -59,7 +59,7 @@ export const actions = {
|
||||
login: ({ dispatch, commit }, data) => {
|
||||
commit('gradidoID', data.gradidoID)
|
||||
commit('language', data.language)
|
||||
// commit('username', data.username)
|
||||
commit('username', data.alias)
|
||||
commit('firstName', data.firstName)
|
||||
commit('lastName', data.lastName)
|
||||
commit('newsletterState', data.klickTipp.newsletterState)
|
||||
@ -71,7 +71,7 @@ export const actions = {
|
||||
},
|
||||
logout: ({ commit, state }) => {
|
||||
commit('token', null)
|
||||
// commit('username', '')
|
||||
commit('username', '')
|
||||
commit('gradidoID', null)
|
||||
commit('firstName', '')
|
||||
commit('lastName', '')
|
||||
|
||||
@ -26,6 +26,7 @@ const {
|
||||
token,
|
||||
firstName,
|
||||
lastName,
|
||||
username,
|
||||
newsletterState,
|
||||
publisherId,
|
||||
isAdmin,
|
||||
@ -104,6 +105,14 @@ describe('Vuex store', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('username', () => {
|
||||
it('sets the state of username', () => {
|
||||
const state = { username: null }
|
||||
username(state, 'peter')
|
||||
expect(state.username).toEqual('peter')
|
||||
})
|
||||
})
|
||||
|
||||
describe('newsletterState', () => {
|
||||
it('sets the state of newsletterState', () => {
|
||||
const state = { newsletterState: null }
|
||||
@ -166,6 +175,7 @@ describe('Vuex store', () => {
|
||||
const commitedData = {
|
||||
gradidoID: 'my-gradido-id',
|
||||
language: 'de',
|
||||
alias: 'peter',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
klickTipp: {
|
||||
@ -180,7 +190,7 @@ describe('Vuex store', () => {
|
||||
|
||||
it('calls eleven commits', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenCalledTimes(10)
|
||||
expect(commit).toHaveBeenCalledTimes(11)
|
||||
})
|
||||
|
||||
it('commits gradidoID', () => {
|
||||
@ -193,44 +203,49 @@ describe('Vuex store', () => {
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'language', 'de')
|
||||
})
|
||||
|
||||
it('commits username', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'username', 'peter')
|
||||
})
|
||||
|
||||
it('commits firstName', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'firstName', 'Peter')
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'firstName', 'Peter')
|
||||
})
|
||||
|
||||
it('commits lastName', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'lastName', 'Lustig')
|
||||
expect(commit).toHaveBeenNthCalledWith(5, 'lastName', 'Lustig')
|
||||
})
|
||||
|
||||
it('commits newsletterState', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(5, 'newsletterState', true)
|
||||
expect(commit).toHaveBeenNthCalledWith(6, 'newsletterState', true)
|
||||
})
|
||||
|
||||
it('commits hasElopage', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(6, 'hasElopage', false)
|
||||
expect(commit).toHaveBeenNthCalledWith(7, 'hasElopage', false)
|
||||
})
|
||||
|
||||
it('commits publisherId', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(7, 'publisherId', 1234)
|
||||
expect(commit).toHaveBeenNthCalledWith(8, 'publisherId', 1234)
|
||||
})
|
||||
|
||||
it('commits isAdmin', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(8, 'isAdmin', true)
|
||||
expect(commit).toHaveBeenNthCalledWith(9, 'isAdmin', true)
|
||||
})
|
||||
|
||||
it('commits hideAmountGDD', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(9, 'hideAmountGDD', false)
|
||||
expect(commit).toHaveBeenNthCalledWith(10, 'hideAmountGDD', false)
|
||||
})
|
||||
|
||||
it('commits hideAmountGDT', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(10, 'hideAmountGDT', true)
|
||||
expect(commit).toHaveBeenNthCalledWith(11, 'hideAmountGDT', true)
|
||||
})
|
||||
})
|
||||
|
||||
@ -240,7 +255,7 @@ describe('Vuex store', () => {
|
||||
|
||||
it('calls eleven commits', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenCalledTimes(10)
|
||||
expect(commit).toHaveBeenCalledTimes(11)
|
||||
})
|
||||
|
||||
it('commits token', () => {
|
||||
@ -248,49 +263,54 @@ describe('Vuex store', () => {
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'token', null)
|
||||
})
|
||||
|
||||
it('commits username', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'username', '')
|
||||
})
|
||||
|
||||
it('commits gradidoID', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'gradidoID', null)
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'gradidoID', null)
|
||||
})
|
||||
|
||||
it('commits firstName', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'firstName', '')
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'firstName', '')
|
||||
})
|
||||
|
||||
it('commits lastName', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'lastName', '')
|
||||
expect(commit).toHaveBeenNthCalledWith(5, 'lastName', '')
|
||||
})
|
||||
|
||||
it('commits newsletterState', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(5, 'newsletterState', null)
|
||||
expect(commit).toHaveBeenNthCalledWith(6, 'newsletterState', null)
|
||||
})
|
||||
|
||||
it('commits hasElopage', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(6, 'hasElopage', false)
|
||||
expect(commit).toHaveBeenNthCalledWith(7, 'hasElopage', false)
|
||||
})
|
||||
|
||||
it('commits publisherId', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(7, 'publisherId', null)
|
||||
expect(commit).toHaveBeenNthCalledWith(8, 'publisherId', null)
|
||||
})
|
||||
|
||||
it('commits isAdmin', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(8, 'isAdmin', false)
|
||||
expect(commit).toHaveBeenNthCalledWith(9, 'isAdmin', false)
|
||||
})
|
||||
|
||||
it('commits hideAmountGDD', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(9, 'hideAmountGDD', false)
|
||||
expect(commit).toHaveBeenNthCalledWith(10, 'hideAmountGDD', false)
|
||||
})
|
||||
|
||||
it('commits hideAmountGDT', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(10, 'hideAmountGDT', true)
|
||||
expect(commit).toHaveBeenNthCalledWith(11, 'hideAmountGDT', true)
|
||||
})
|
||||
// how to get this working?
|
||||
it.skip('calls localStorage.clear()', () => {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { configure, extend } from 'vee-validate'
|
||||
// eslint-disable-next-line camelcase
|
||||
import { required, email, min, max, is_not } from 'vee-validate/dist/rules'
|
||||
import { checkUsername } from '@/graphql/queries'
|
||||
|
||||
export const loadAllRules = (i18nCallback) => {
|
||||
export const loadAllRules = (i18nCallback, apollo) => {
|
||||
configure({
|
||||
defaultMessage: (field, values) => {
|
||||
// eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys
|
||||
@ -96,7 +97,7 @@ export const loadAllRules = (i18nCallback) => {
|
||||
message: (_, values) => i18nCallback.t('site.signup.one_number', values),
|
||||
})
|
||||
|
||||
extend('atLeastEightCharactera', {
|
||||
extend('atLeastEightCharacters', {
|
||||
validate(value) {
|
||||
return !!value.match(/.{8,}/)
|
||||
},
|
||||
@ -123,4 +124,35 @@ export const loadAllRules = (i18nCallback) => {
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('site.signup.dont_match', values),
|
||||
})
|
||||
|
||||
extend('usernameAllowedChars', {
|
||||
validate(value) {
|
||||
return !!value.match(/^[a-zA-Z0-9_-]+$/)
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('form.validation.username-allowed-chars', values),
|
||||
})
|
||||
|
||||
extend('usernameHyphens', {
|
||||
validate(value) {
|
||||
return !!value.match(/^[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+?)*$/)
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('form.validation.username-hyphens', values),
|
||||
})
|
||||
|
||||
extend('usernameUnique', {
|
||||
validate(value) {
|
||||
if (!value.match(/^(?=.{3,20}$)[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+?)*$/)) return true
|
||||
return apollo
|
||||
.query({
|
||||
query: checkUsername,
|
||||
variables: { username: value },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
return {
|
||||
valid: data.checkUsername,
|
||||
}
|
||||
})
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('form.validation.username-unique', values),
|
||||
})
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ const i18nMock = {
|
||||
n: (value, format) => value,
|
||||
}
|
||||
|
||||
loadAllRules(i18nMock)
|
||||
loadAllRules(i18nMock, { query: jest.fn().mockResolvedValue({ data: { checkUsername: true } }) })
|
||||
|
||||
global.localVue = createLocalVue()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user