mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #4270 from Ocelot-Social-Community/4092-implement-new-registration
feat: 🍰Implement Registration Slider
This commit is contained in:
commit
9ad7dab918
@ -71,6 +71,5 @@ export default {
|
||||
AddEmailAddress: sendEmailVerificationMail,
|
||||
requestPasswordReset: sendPasswordResetMail,
|
||||
Signup: sendSignupMail,
|
||||
SignupByInvitation: sendSignupMail,
|
||||
},
|
||||
}
|
||||
|
||||
@ -15,9 +15,11 @@ const defaultParams = {
|
||||
|
||||
export const signupTemplate = ({ email, nonce }) => {
|
||||
const subject = `Willkommen, Bienvenue, Welcome to ${CONFIG.APPLICATION_NAME}!`
|
||||
const actionUrl = new URL('/registration/create-user-account', CONFIG.CLIENT_URI)
|
||||
actionUrl.searchParams.set('nonce', nonce)
|
||||
// dev format example: http://localhost:3000/registration?method=invite-mail&email=wolle.huss%40pjannto.com&nonce=64853
|
||||
const actionUrl = new URL('/registration', CONFIG.CLIENT_URI)
|
||||
actionUrl.searchParams.set('method', 'invite-mail')
|
||||
actionUrl.searchParams.set('email', email)
|
||||
actionUrl.searchParams.set('nonce', nonce)
|
||||
|
||||
return {
|
||||
from,
|
||||
@ -34,8 +36,8 @@ export const signupTemplate = ({ email, nonce }) => {
|
||||
export const emailVerificationTemplate = ({ email, nonce, name }) => {
|
||||
const subject = 'Neue E-Mail Adresse | New E-Mail Address'
|
||||
const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI)
|
||||
actionUrl.searchParams.set('nonce', nonce)
|
||||
actionUrl.searchParams.set('email', email)
|
||||
actionUrl.searchParams.set('nonce', nonce)
|
||||
|
||||
return {
|
||||
from,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { rule, shield, deny, allow, or } from 'graphql-shield'
|
||||
import { getNeode } from '../db/neo4j'
|
||||
import CONFIG from '../config'
|
||||
import { validateInviteCode } from '../schema/resolvers/transactions/inviteCodes'
|
||||
|
||||
const debug = !!CONFIG.DEBUG
|
||||
const allowExternalErrors = true
|
||||
@ -89,6 +90,13 @@ const noEmailFilter = rule({
|
||||
|
||||
const publicRegistration = rule()(() => CONFIG.PUBLIC_REGISTRATION)
|
||||
|
||||
const inviteRegistration = rule()(async (_parent, args, { user, driver }) => {
|
||||
if (!CONFIG.INVITE_REGISTRATION) return false
|
||||
const { inviteCode } = args
|
||||
const session = driver.session()
|
||||
return validateInviteCode(session, inviteCode)
|
||||
})
|
||||
|
||||
// Permissions
|
||||
export default shield(
|
||||
{
|
||||
@ -121,6 +129,7 @@ export default shield(
|
||||
userData: isAuthenticated,
|
||||
MyInviteCodes: isAuthenticated,
|
||||
isValidInviteCode: allow,
|
||||
VerifyNonce: allow,
|
||||
queryLocations: isAuthenticated,
|
||||
availableRoles: isAdmin,
|
||||
getInviteCode: isAuthenticated, // and inviteRegistration
|
||||
@ -128,8 +137,7 @@ export default shield(
|
||||
Mutation: {
|
||||
'*': deny,
|
||||
login: allow,
|
||||
SignupByInvitation: allow,
|
||||
Signup: or(publicRegistration, isAdmin),
|
||||
Signup: or(publicRegistration, inviteRegistration, isAdmin),
|
||||
SignupVerification: allow,
|
||||
UpdateUser: onlyYourself,
|
||||
CreatePost: isAuthenticated,
|
||||
|
||||
@ -3,11 +3,13 @@ import createServer from '../server'
|
||||
import Factory, { cleanDatabase } from '../db/factories'
|
||||
import { gql } from '../helpers/jest'
|
||||
import { getDriver, getNeode } from '../db/neo4j'
|
||||
import CONFIG from '../config'
|
||||
|
||||
const instance = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
let query, authenticatedUser, owner, anotherRegularUser, administrator, variables, moderator
|
||||
let query, mutate, variables
|
||||
let authenticatedUser, owner, anotherRegularUser, administrator, moderator
|
||||
|
||||
describe('authorization', () => {
|
||||
beforeAll(async () => {
|
||||
@ -20,6 +22,7 @@ describe('authorization', () => {
|
||||
}),
|
||||
})
|
||||
query = createTestClient(server).query
|
||||
mutate = createTestClient(server).mutate
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@ -159,5 +162,132 @@ describe('authorization', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('access Signup', () => {
|
||||
const signupMutation = gql`
|
||||
mutation($email: String!, $inviteCode: String) {
|
||||
Signup(email: $email, inviteCode: $inviteCode) {
|
||||
email
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('admin invite only', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
email: 'some@email.org',
|
||||
inviteCode: 'AAAAAA',
|
||||
}
|
||||
CONFIG.INVITE_REGISTRATION = false
|
||||
CONFIG.PUBLIC_REGISTRATION = false
|
||||
await Factory.build('inviteCode', {
|
||||
code: 'AAAAAA',
|
||||
})
|
||||
})
|
||||
|
||||
describe('as user', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await anotherRegularUser.toJson()
|
||||
})
|
||||
|
||||
it('denies permission', async () => {
|
||||
await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
data: { Signup: null },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('as admin', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await administrator.toJson()
|
||||
})
|
||||
|
||||
it('returns an email', async () => {
|
||||
await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
|
||||
errors: undefined,
|
||||
data: {
|
||||
Signup: { email: 'some@email.org' },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('public registration', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
email: 'some@email.org',
|
||||
inviteCode: 'AAAAAA',
|
||||
}
|
||||
CONFIG.INVITE_REGISTRATION = false
|
||||
CONFIG.PUBLIC_REGISTRATION = true
|
||||
await Factory.build('inviteCode', {
|
||||
code: 'AAAAAA',
|
||||
})
|
||||
})
|
||||
|
||||
describe('as anyone', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('returns an email', async () => {
|
||||
await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
|
||||
errors: undefined,
|
||||
data: {
|
||||
Signup: { email: 'some@email.org' },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('invite registration', () => {
|
||||
beforeEach(async () => {
|
||||
CONFIG.INVITE_REGISTRATION = true
|
||||
CONFIG.PUBLIC_REGISTRATION = false
|
||||
await Factory.build('inviteCode', {
|
||||
code: 'AAAAAA',
|
||||
})
|
||||
})
|
||||
|
||||
describe('as anyone with valid invite code', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
email: 'some@email.org',
|
||||
inviteCode: 'AAAAAA',
|
||||
}
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('returns an email', async () => {
|
||||
await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
|
||||
errors: undefined,
|
||||
data: {
|
||||
Signup: { email: 'some@email.org' },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('as anyone without valid invite', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
email: 'some@email.org',
|
||||
inviteCode: 'no valid invite code',
|
||||
}
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('denies permission', async () => {
|
||||
await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
data: { Signup: null },
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -6,6 +6,27 @@ import Validator from 'neode/build/Services/Validator.js'
|
||||
import normalizeEmail from './helpers/normalizeEmail'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
VerifyNonce: async (_parent, args, context, _resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const result = await txc.run(
|
||||
`
|
||||
MATCH (email:EmailAddress {email: $email, nonce: $nonce})
|
||||
RETURN count(email) > 0 AS result
|
||||
`,
|
||||
{ email: args.email, nonce: args.nonce },
|
||||
)
|
||||
return result
|
||||
})
|
||||
try {
|
||||
const txResult = await readTxResultPromise
|
||||
return txResult.records[0].get('result')
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
AddEmailAddress: async (_parent, args, context, _resolveInfo) => {
|
||||
let response
|
||||
|
||||
@ -6,7 +6,7 @@ import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
let mutate
|
||||
let mutate, query
|
||||
let authenticatedUser
|
||||
let user
|
||||
let variables
|
||||
@ -16,7 +16,8 @@ beforeEach(async () => {
|
||||
variables = {}
|
||||
})
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
@ -27,6 +28,7 @@ beforeAll(() => {
|
||||
},
|
||||
})
|
||||
mutate = createTestClient(server).mutate
|
||||
query = createTestClient(server).query
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@ -185,7 +187,7 @@ describe('VerifyEmailAddress', () => {
|
||||
let emailAddress
|
||||
beforeEach(async () => {
|
||||
emailAddress = await Factory.build('unverifiedEmailAddress', {
|
||||
nonce: 'abcdef',
|
||||
nonce: '12345',
|
||||
verifiedAt: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
email: 'to-be-verified@example.org',
|
||||
@ -204,7 +206,7 @@ describe('VerifyEmailAddress', () => {
|
||||
|
||||
describe('given valid nonce for `UnverifiedEmailAddress` node', () => {
|
||||
beforeEach(() => {
|
||||
variables = { ...variables, nonce: 'abcdef' }
|
||||
variables = { ...variables, nonce: '12345' }
|
||||
})
|
||||
|
||||
describe('but the address does not belong to the authenticated user', () => {
|
||||
@ -295,3 +297,40 @@ describe('VerifyEmailAddress', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('VerifyNonce', () => {
|
||||
beforeEach(async () => {
|
||||
await Factory.build('emailAddress', {
|
||||
nonce: '12345',
|
||||
verifiedAt: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
email: 'to-be-verified@example.org',
|
||||
})
|
||||
})
|
||||
|
||||
const verifyNonceQuery = gql`
|
||||
query($email: String!, $nonce: String!) {
|
||||
VerifyNonce(email: $email, nonce: $nonce)
|
||||
}
|
||||
`
|
||||
|
||||
it('returns true when nonce and email match', async () => {
|
||||
variables = {
|
||||
email: 'to-be-verified@example.org',
|
||||
nonce: '12345',
|
||||
}
|
||||
await expect(query({ query: verifyNonceQuery, variables })).resolves.toMatchObject({
|
||||
data: { VerifyNonce: true },
|
||||
})
|
||||
})
|
||||
|
||||
it('returns false when nonce and email do not match', async () => {
|
||||
variables = {
|
||||
email: 'to-be-verified@example.org',
|
||||
nonce: '---',
|
||||
}
|
||||
await expect(query({ query: verifyNonceQuery, variables })).resolves.toMatchObject({
|
||||
data: { VerifyNonce: false },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { v4 as uuid } from 'uuid'
|
||||
export default function generateNonce() {
|
||||
return uuid().substring(0, 6)
|
||||
return Array.from({ length: 5 }, (n = Math.floor(Math.random() * 10)) => {
|
||||
return String.fromCharCode(n + 48)
|
||||
}).join('')
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import generateInviteCode from './helpers/generateInviteCode'
|
||||
import Resolver from './helpers/Resolver'
|
||||
import { validateInviteCode } from './transactions/inviteCodes'
|
||||
|
||||
const uniqueInviteCode = async (session, code) => {
|
||||
return session.readTransaction(async (txc) => {
|
||||
@ -82,28 +83,9 @@ export default {
|
||||
},
|
||||
isValidInviteCode: async (_parent, args, context, _resolveInfo) => {
|
||||
const { code } = args
|
||||
if (!code) return false
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const result = await txc.run(
|
||||
`MATCH (ic:InviteCode { code: toUpper($code) })
|
||||
RETURN
|
||||
CASE
|
||||
WHEN ic.expiresAt IS NULL THEN true
|
||||
WHEN datetime(ic.expiresAt) >= datetime() THEN true
|
||||
ELSE false END AS result`,
|
||||
{
|
||||
code,
|
||||
},
|
||||
)
|
||||
return result.records.map((record) => record.get('result'))
|
||||
})
|
||||
try {
|
||||
const txResult = await readTxResultPromise
|
||||
return !!txResult[0]
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
if (!code) return false
|
||||
return validateInviteCode(session, code)
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
|
||||
@ -29,34 +29,22 @@ export default {
|
||||
}
|
||||
args.termsAndConditionsAgreedAt = new Date().toISOString()
|
||||
|
||||
let { nonce, email } = args
|
||||
let { nonce, email, inviteCode } = args
|
||||
email = normalizeEmail(email)
|
||||
delete args.nonce
|
||||
delete args.email
|
||||
delete args.inviteCode
|
||||
args = encryptPassword(args)
|
||||
|
||||
const { driver } = context
|
||||
const session = driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const createUserTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH(email:EmailAddress {nonce: $nonce, email: $email})
|
||||
WHERE NOT (email)-[:BELONGS_TO]->()
|
||||
CREATE (user:User)
|
||||
MERGE(user)-[:PRIMARY_EMAIL]->(email)
|
||||
MERGE(user)<-[:BELONGS_TO]-(email)
|
||||
SET user += $args
|
||||
SET user.id = randomUUID()
|
||||
SET user.role = 'user'
|
||||
SET user.createdAt = toString(datetime())
|
||||
SET user.updatedAt = toString(datetime())
|
||||
SET user.allowEmbedIframes = FALSE
|
||||
SET user.showShoutsPublicly = FALSE
|
||||
SET email.verifiedAt = toString(datetime())
|
||||
RETURN user {.*}
|
||||
`,
|
||||
{ args, nonce, email },
|
||||
)
|
||||
const createUserTransactionResponse = await transaction.run(signupCypher(inviteCode), {
|
||||
args,
|
||||
nonce,
|
||||
email,
|
||||
inviteCode,
|
||||
})
|
||||
const [user] = createUserTransactionResponse.records.map((record) => record.get('user'))
|
||||
if (!user) throw new UserInputError('Invalid email or nonce')
|
||||
return user
|
||||
@ -74,3 +62,39 @@ export default {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const signupCypher = (inviteCode) => {
|
||||
let optionalMatch = ''
|
||||
let optionalMerge = ''
|
||||
if (inviteCode) {
|
||||
optionalMatch = `
|
||||
OPTIONAL MATCH
|
||||
(inviteCode:InviteCode {code: $inviteCode})<-[:GENERATED]-(host:User)
|
||||
`
|
||||
optionalMerge = `
|
||||
MERGE(user)-[:REDEEMED]->(inviteCode)
|
||||
MERGE(host)-[:INVITED]->(user)
|
||||
MERGE(user)-[:FOLLOWS]->(host)
|
||||
MERGE(host)-[:FOLLOWS]->(user)
|
||||
`
|
||||
}
|
||||
const cypher = `
|
||||
MATCH(email:EmailAddress {nonce: $nonce, email: $email})
|
||||
WHERE NOT (email)-[:BELONGS_TO]->()
|
||||
${optionalMatch}
|
||||
CREATE (user:User)
|
||||
MERGE(user)-[:PRIMARY_EMAIL]->(email)
|
||||
MERGE(user)<-[:BELONGS_TO]-(email)
|
||||
${optionalMerge}
|
||||
SET user += $args
|
||||
SET user.id = randomUUID()
|
||||
SET user.role = 'user'
|
||||
SET user.createdAt = toString(datetime())
|
||||
SET user.updatedAt = toString(datetime())
|
||||
SET user.allowEmbedIframes = FALSE
|
||||
SET user.showShoutsPublicly = FALSE
|
||||
SET email.verifiedAt = toString(datetime())
|
||||
RETURN user {.*}
|
||||
`
|
||||
return cypher
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { gql } from '../../helpers/jest'
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import CONFIG from '../../config'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
@ -15,7 +16,8 @@ beforeEach(async () => {
|
||||
variables = {}
|
||||
})
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
@ -34,8 +36,8 @@ afterEach(async () => {
|
||||
|
||||
describe('Signup', () => {
|
||||
const mutation = gql`
|
||||
mutation($email: String!) {
|
||||
Signup(email: $email) {
|
||||
mutation($email: String!, $inviteCode: String) {
|
||||
Signup(email: $email, inviteCode: $inviteCode) {
|
||||
email
|
||||
}
|
||||
}
|
||||
@ -50,6 +52,8 @@ describe('Signup', () => {
|
||||
})
|
||||
|
||||
it('throws AuthorizationError', async () => {
|
||||
CONFIG.INVITE_REGISTRATION = false
|
||||
CONFIG.PUBLIC_REGISTRATION = false
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
|
||||
22
backend/src/schema/resolvers/transactions/inviteCodes.js
Normal file
22
backend/src/schema/resolvers/transactions/inviteCodes.js
Normal file
@ -0,0 +1,22 @@
|
||||
export async function validateInviteCode(session, inviteCode) {
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
const result = await txc.run(
|
||||
`MATCH (ic:InviteCode { code: toUpper($inviteCode) })
|
||||
RETURN
|
||||
CASE
|
||||
WHEN ic.expiresAt IS NULL THEN true
|
||||
WHEN datetime(ic.expiresAt) >= datetime() THEN true
|
||||
ELSE false END AS result`,
|
||||
{
|
||||
inviteCode,
|
||||
},
|
||||
)
|
||||
return result.records.map((record) => record.get('result'))
|
||||
})
|
||||
try {
|
||||
const txResult = await readTxResultPromise
|
||||
return !!txResult[0]
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -4,12 +4,16 @@ type EmailAddress {
|
||||
createdAt: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
VerifyNonce(email: String!, nonce: String!): Boolean!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
Signup(email: String!): EmailAddress
|
||||
SignupByInvitation(email: String!, token: String!): EmailAddress
|
||||
Signup(email: String!, inviteCode: String = null): EmailAddress
|
||||
SignupVerification(
|
||||
nonce: String!
|
||||
email: String!
|
||||
inviteCode: String = null
|
||||
name: String!
|
||||
password: String!
|
||||
slug: String
|
||||
|
||||
@ -2,18 +2,38 @@
|
||||
<div class="Sliders">
|
||||
<slot :name="'header'" />
|
||||
|
||||
<ds-heading v-if="sliderData.sliders[sliderIndex].title" size="h3">
|
||||
{{ sliderData.sliders[sliderIndex].title }}
|
||||
<ds-heading
|
||||
v-if="
|
||||
sliderData.sliders[sliderIndex].titleIdent &&
|
||||
((typeof sliderData.sliders[sliderIndex].titleIdent === 'string' &&
|
||||
$t(sliderData.sliders[sliderIndex].titleIdent).length > 0) ||
|
||||
(typeof sliderData.sliders[sliderIndex].titleIdent === 'object' &&
|
||||
$t(
|
||||
sliderData.sliders[sliderIndex].titleIdent.id,
|
||||
sliderData.sliders[sliderIndex].titleIdent.data,
|
||||
).length > 0))
|
||||
"
|
||||
size="h3"
|
||||
>
|
||||
{{
|
||||
(typeof sliderData.sliders[sliderIndex].titleIdent === 'string' &&
|
||||
$t(sliderData.sliders[sliderIndex].titleIdent)) ||
|
||||
(typeof sliderData.sliders[sliderIndex].titleIdent === 'object' &&
|
||||
$t(
|
||||
sliderData.sliders[sliderIndex].titleIdent.id,
|
||||
sliderData.sliders[sliderIndex].titleIdent.data,
|
||||
))
|
||||
}}
|
||||
</ds-heading>
|
||||
|
||||
<slot :name="sliderData.sliders[sliderIndex].name" />
|
||||
|
||||
<ds-flex>
|
||||
<ds-flex-item :centered="true">
|
||||
<ds-flex-item v-if="multipleSliders" :centered="true">
|
||||
<div
|
||||
v-for="(slider, index) in sliderData.sliders"
|
||||
:key="slider.name"
|
||||
:class="['Sliders__slider-selection', index < sliderIndex && '--confirmed']"
|
||||
:class="['Sliders__slider-selection', index === sliderIndex && '--unconfirmed']"
|
||||
>
|
||||
<base-button
|
||||
:class="['selection-dot']"
|
||||
@ -30,15 +50,20 @@
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<base-button
|
||||
style="float: right"
|
||||
:style="multipleSliders && 'float: right'"
|
||||
:icon="sliderData.sliders[sliderIndex].button.icon"
|
||||
type="submit"
|
||||
filled
|
||||
:loading="false"
|
||||
:loading="
|
||||
sliderData.sliders[sliderIndex].button.loading !== undefined
|
||||
? sliderData.sliders[sliderIndex].button.loading
|
||||
: false
|
||||
"
|
||||
:disabled="!sliderData.sliders[sliderIndex].validated"
|
||||
@click="onNextClick"
|
||||
data-test="next-button"
|
||||
>
|
||||
{{ sliderData.sliders[sliderIndex].button.title }}
|
||||
{{ $t(sliderData.sliders[sliderIndex].button.titleIdent) }}
|
||||
</base-button>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
@ -57,6 +82,9 @@ export default {
|
||||
sliderIndex() {
|
||||
return this.sliderData.sliderIndex // to have a shorter notation
|
||||
},
|
||||
multipleSliders() {
|
||||
return this.sliderData.sliders.length > 1
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async onNextClick() {
|
||||
@ -79,7 +107,7 @@ export default {
|
||||
.selection-dot {
|
||||
margin-right: 2px;
|
||||
}
|
||||
&.--confirmed {
|
||||
&.--unconfirmed {
|
||||
opacity: $opacity-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,12 +37,12 @@ describe('EnterNonce ', () => {
|
||||
describe('after nonce entered', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('input#nonce').setValue('123456')
|
||||
wrapper.find('input#nonce').setValue('12345')
|
||||
wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('emits `nonceEntered`', () => {
|
||||
const expected = [[{ nonce: '123456', email: 'mail@example.org' }]]
|
||||
const expected = [[{ nonce: '12345', email: 'mail@example.org' }]]
|
||||
expect(wrapper.emitted('nonceEntered')).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,17 +8,17 @@
|
||||
@input-valid="handleInputValid"
|
||||
>
|
||||
<ds-input
|
||||
:placeholder="$t('components.enter-nonce.form.nonce')"
|
||||
:placeholder="$t('components.registration.email-nonce.form.nonce')"
|
||||
model="nonce"
|
||||
name="nonce"
|
||||
id="nonce"
|
||||
icon="question-circle"
|
||||
/>
|
||||
<ds-text>
|
||||
{{ $t('components.enter-nonce.form.description') }}
|
||||
{{ $t('components.registration.email-nonce.form.description') }}
|
||||
</ds-text>
|
||||
<base-button :disabled="disabled" filled name="submit" type="submit">
|
||||
{{ $t('components.enter-nonce.form.next') }}
|
||||
{{ $t('components.registration.email-nonce.form.next') }}
|
||||
</base-button>
|
||||
<slot></slot>
|
||||
</ds-form>
|
||||
@ -37,10 +37,10 @@ export default {
|
||||
formSchema: {
|
||||
nonce: {
|
||||
type: 'string',
|
||||
min: 6,
|
||||
max: 6,
|
||||
min: 5,
|
||||
max: 5,
|
||||
required: true,
|
||||
message: this.$t('components.enter-nonce.form.validations.length'),
|
||||
message: this.$t('components.registration.email-nonce.form.validations.length'),
|
||||
},
|
||||
},
|
||||
disabled: true,
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
</base-button>
|
||||
<p>
|
||||
{{ $t('login.no-account') }}
|
||||
<nuxt-link to="/registration/signup">{{ $t('login.register') }}</nuxt-link>
|
||||
<nuxt-link to="/registration">{{ $t('login.register') }}</nuxt-link>
|
||||
</p>
|
||||
</form>
|
||||
<template #topMenu>
|
||||
|
||||
@ -1,153 +0,0 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import Vue from 'vue'
|
||||
import { VERSION } from '~/constants/terms-and-conditions-version.js'
|
||||
import CreateUserAccount from './CreateUserAccount'
|
||||
import { SignupVerificationMutation } from '~/graphql/Registration.js'
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['sweetalert-icon'] = '<span><slot /></span>'
|
||||
config.stubs['client-only'] = '<span><slot /></span>'
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
|
||||
describe('CreateUserAccount', () => {
|
||||
let wrapper, Wrapper, mocks, propsData, stubs
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
$t: jest.fn(),
|
||||
$apollo: {
|
||||
loading: false,
|
||||
mutate: jest.fn(),
|
||||
},
|
||||
$i18n: {
|
||||
locale: () => 'en',
|
||||
},
|
||||
}
|
||||
propsData = {}
|
||||
stubs = {
|
||||
LocaleSwitch: "<div class='stub'></div>",
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
Wrapper = () => {
|
||||
return mount(CreateUserAccount, {
|
||||
mocks,
|
||||
propsData,
|
||||
localVue,
|
||||
stubs,
|
||||
})
|
||||
}
|
||||
|
||||
describe('given email and nonce', () => {
|
||||
beforeEach(() => {
|
||||
propsData.nonce = '666777'
|
||||
propsData.email = 'sixseven@example.org'
|
||||
})
|
||||
|
||||
it('renders a form to create a new user', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.create-user-account').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('submit', () => {
|
||||
let action
|
||||
beforeEach(() => {
|
||||
action = async () => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('input#name').setValue('John Doe')
|
||||
wrapper.find('input#password').setValue('hellopassword')
|
||||
wrapper.find('textarea#about').setValue('Hello I am the `about` attribute')
|
||||
wrapper.find('input#passwordConfirmation').setValue('hellopassword')
|
||||
wrapper.find('input#checkbox0').setChecked()
|
||||
wrapper.find('input#checkbox1').setChecked()
|
||||
wrapper.find('input#checkbox2').setChecked()
|
||||
wrapper.find('input#checkbox3').setChecked()
|
||||
wrapper.find('input#checkbox4').setChecked()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await wrapper.html()
|
||||
}
|
||||
})
|
||||
|
||||
it('delivers data to backend', async () => {
|
||||
await action()
|
||||
const expected = expect.objectContaining({
|
||||
variables: {
|
||||
about: 'Hello I am the `about` attribute',
|
||||
name: 'John Doe',
|
||||
email: 'sixseven@example.org',
|
||||
nonce: '666777',
|
||||
password: 'hellopassword',
|
||||
termsAndConditionsAgreedVersion: VERSION,
|
||||
locale: 'en',
|
||||
},
|
||||
})
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
it('calls CreateUserAccount graphql mutation', async () => {
|
||||
await action()
|
||||
const expected = expect.objectContaining({ mutation: SignupVerificationMutation })
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
describe('in case mutation resolves', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$apollo.mutate = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
SignupVerification: {
|
||||
id: 'u1',
|
||||
name: 'John Doe',
|
||||
slug: 'john-doe',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('displays success', async () => {
|
||||
await action()
|
||||
await Vue.nextTick()
|
||||
expect(mocks.$t).toHaveBeenCalledWith(
|
||||
'components.registration.create-user-account.success',
|
||||
)
|
||||
})
|
||||
|
||||
describe('after timeout', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
it('emits `userCreated` with { password, email }', async () => {
|
||||
await action()
|
||||
jest.runAllTimers()
|
||||
expect(wrapper.emitted('userCreated')).toEqual([
|
||||
[
|
||||
{
|
||||
email: 'sixseven@example.org',
|
||||
password: 'hellopassword',
|
||||
},
|
||||
],
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('in case mutation rejects', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$apollo.mutate = jest.fn().mockRejectedValue(new Error('Invalid nonce'))
|
||||
})
|
||||
|
||||
it('displays form errors', async () => {
|
||||
await action()
|
||||
await Vue.nextTick()
|
||||
expect(mocks.$t).toHaveBeenCalledWith(
|
||||
'components.registration.create-user-account.error',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,84 +0,0 @@
|
||||
import { storiesOf } from '@storybook/vue'
|
||||
import { withA11y } from '@storybook/addon-a11y'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import Vuex from 'vuex'
|
||||
import helpers from '~/storybook/helpers'
|
||||
import links from '~/constants/links.js'
|
||||
import metadata from '~/constants/metadata.js'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||
import CreateUserAccount from './CreateUserAccount.vue'
|
||||
|
||||
helpers.init()
|
||||
|
||||
const createStore = ({ loginSuccess }) => {
|
||||
return new Vuex.Store({
|
||||
modules: {
|
||||
auth: {
|
||||
namespaced: true,
|
||||
state: () => ({
|
||||
pending: false,
|
||||
}),
|
||||
mutations: {
|
||||
SET_PENDING(state, pending) {
|
||||
state.pending = pending
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
pending(state) {
|
||||
return !!state.pending
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async login({ commit, dispatch }, args) {
|
||||
action('Vuex action `auth/login`')(args)
|
||||
return new Promise((resolve, reject) => {
|
||||
commit('SET_PENDING', true)
|
||||
setTimeout(() => {
|
||||
commit('SET_PENDING', false)
|
||||
if (loginSuccess) {
|
||||
resolve(loginSuccess)
|
||||
} else {
|
||||
reject(new Error('Login unsuccessful'))
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
storiesOf('CreateUserAccount', module)
|
||||
.addDecorator(withA11y)
|
||||
.addDecorator(helpers.layout)
|
||||
.add('standard', () => ({
|
||||
components: { LocaleSwitch, CreateUserAccount },
|
||||
store: createStore({ loginSuccess: true }),
|
||||
data: () => ({
|
||||
links,
|
||||
metadata,
|
||||
nonce: 'A34RB56',
|
||||
email: 'user@example.org',
|
||||
}),
|
||||
methods: {
|
||||
handleSuccess() {
|
||||
action('You are logged in!')()
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<ds-container width="small">
|
||||
<base-card>
|
||||
<template #imageColumn>
|
||||
<a :href="links.ORGANIZATION" :title="$t('login.moreInfo', metadata)" target="_blank">
|
||||
<img class="image" alt="Sign up" src="/img/custom/sign-up.svg" />
|
||||
</a>
|
||||
</template>
|
||||
<create-user-account @userCreated="handleSuccess" :email="email" :nonce="nonce" />
|
||||
<template #topMenu>
|
||||
<locale-switch offset="5" />
|
||||
</template>
|
||||
</base-card>
|
||||
</ds-container>
|
||||
`,
|
||||
}))
|
||||
@ -1,222 +0,0 @@
|
||||
<template>
|
||||
<div v-if="response === 'success'">
|
||||
<transition name="ds-transition-fade">
|
||||
<sweetalert-icon icon="success" />
|
||||
</transition>
|
||||
<ds-text align="center" bold color="success">
|
||||
{{ $t('components.registration.create-user-account.success') }}
|
||||
</ds-text>
|
||||
</div>
|
||||
<div v-else-if="response === 'error'">
|
||||
<transition name="ds-transition-fade">
|
||||
<sweetalert-icon icon="error" />
|
||||
</transition>
|
||||
<ds-text align="center" bold color="danger">
|
||||
{{ $t('components.registration.create-user-account.error') }}
|
||||
</ds-text>
|
||||
<ds-text align="center">
|
||||
{{ $t('components.registration.create-user-account.help') }}
|
||||
<a :href="'mailto:' + supportEmail">{{ supportEmail }}</a>
|
||||
</ds-text>
|
||||
<ds-space centered>
|
||||
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
|
||||
</ds-space>
|
||||
</div>
|
||||
<div v-else class="create-account-card">
|
||||
<ds-space margin-top="large">
|
||||
<ds-heading size="h3">
|
||||
{{ $t('components.registration.create-user-account.title') }}
|
||||
</ds-heading>
|
||||
</ds-space>
|
||||
|
||||
<ds-form class="create-user-account" v-model="formData" :schema="formSchema" @submit="submit">
|
||||
<template v-slot="{ errors }">
|
||||
<ds-input
|
||||
id="name"
|
||||
model="name"
|
||||
icon="user"
|
||||
:label="$t('settings.data.labelName')"
|
||||
:placeholder="$t('settings.data.namePlaceholder')"
|
||||
/>
|
||||
<ds-input
|
||||
id="about"
|
||||
model="about"
|
||||
type="textarea"
|
||||
rows="3"
|
||||
:label="$t('settings.data.labelBio')"
|
||||
:placeholder="$t('settings.data.labelBio')"
|
||||
/>
|
||||
<ds-input
|
||||
id="password"
|
||||
model="password"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:label="$t('settings.security.change-password.label-new-password')"
|
||||
/>
|
||||
<ds-input
|
||||
id="passwordConfirmation"
|
||||
model="passwordConfirmation"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:label="$t('settings.security.change-password.label-new-password-confirm')"
|
||||
/>
|
||||
<password-strength :password="formData.password" />
|
||||
|
||||
<ds-text>
|
||||
<input
|
||||
id="checkbox0"
|
||||
type="checkbox"
|
||||
v-model="termsAndConditionsConfirmed"
|
||||
:checked="termsAndConditionsConfirmed"
|
||||
/>
|
||||
<label for="checkbox0">
|
||||
{{ $t('termsAndConditions.termsAndConditionsConfirmed') }}
|
||||
<br />
|
||||
<nuxt-link to="/terms-and-conditions">{{ $t('site.termsAndConditions') }}</nuxt-link>
|
||||
</label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox1" type="checkbox" v-model="dataPrivacy" :checked="dataPrivacy" />
|
||||
<label for="checkbox1">
|
||||
{{ $t('components.registration.signup.form.data-privacy') }}
|
||||
<br />
|
||||
<nuxt-link to="/data-privacy">
|
||||
{{ $t('site.data-privacy') }}
|
||||
</nuxt-link>
|
||||
</label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox2" type="checkbox" v-model="minimumAge" :checked="minimumAge" />
|
||||
<label
|
||||
for="checkbox2"
|
||||
v-html="$t('components.registration.signup.form.minimum-age')"
|
||||
></label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox3" type="checkbox" v-model="noCommercial" :checked="noCommercial" />
|
||||
<label
|
||||
for="checkbox3"
|
||||
v-html="$t('components.registration.signup.form.no-commercial')"
|
||||
></label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox4" type="checkbox" v-model="noPolitical" :checked="noPolitical" />
|
||||
<label
|
||||
for="checkbox4"
|
||||
v-html="$t('components.registration.signup.form.no-political')"
|
||||
></label>
|
||||
</ds-text>
|
||||
<base-button
|
||||
style="float: right"
|
||||
icon="check"
|
||||
type="submit"
|
||||
filled
|
||||
:loading="$apollo.loading"
|
||||
:disabled="
|
||||
errors ||
|
||||
!termsAndConditionsConfirmed ||
|
||||
!dataPrivacy ||
|
||||
!minimumAge ||
|
||||
!noCommercial ||
|
||||
!noPolitical
|
||||
"
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
</base-button>
|
||||
</template>
|
||||
</ds-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import links from '~/constants/links'
|
||||
import PasswordStrength from '../Password/Strength'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
import PasswordForm from '~/components/utils/PasswordFormHelper'
|
||||
import { VERSION } from '~/constants/terms-and-conditions-version.js'
|
||||
import { SignupVerificationMutation } from '~/graphql/Registration.js'
|
||||
import emails from '~/constants/emails'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PasswordStrength,
|
||||
SweetalertIcon,
|
||||
},
|
||||
data() {
|
||||
const passwordForm = PasswordForm({ translate: this.$t })
|
||||
return {
|
||||
links,
|
||||
supportEmail: emails.SUPPORT,
|
||||
formData: {
|
||||
name: '',
|
||||
about: '',
|
||||
...passwordForm.formData,
|
||||
},
|
||||
formSchema: {
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
min: 3,
|
||||
},
|
||||
about: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
...passwordForm.formSchema,
|
||||
},
|
||||
disabled: true,
|
||||
response: null,
|
||||
// TODO: Our styleguide does not support checkmarks.
|
||||
// Integrate termsAndConditionsConfirmed into `this.formData` once we
|
||||
// have checkmarks available.
|
||||
termsAndConditionsConfirmed: false,
|
||||
dataPrivacy: false,
|
||||
minimumAge: false,
|
||||
noCommercial: false,
|
||||
noPolitical: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
nonce: { type: String, required: true },
|
||||
email: { type: String, required: true },
|
||||
},
|
||||
methods: {
|
||||
async submit() {
|
||||
const { name, password, about } = this.formData
|
||||
const { email, nonce } = this
|
||||
const termsAndConditionsAgreedVersion = VERSION
|
||||
const locale = this.$i18n.locale()
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: SignupVerificationMutation,
|
||||
variables: {
|
||||
name,
|
||||
password,
|
||||
about,
|
||||
email,
|
||||
nonce,
|
||||
termsAndConditionsAgreedVersion,
|
||||
locale,
|
||||
},
|
||||
})
|
||||
this.response = 'success'
|
||||
setTimeout(() => {
|
||||
this.$emit('userCreated', {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
}, 3000)
|
||||
} catch (err) {
|
||||
this.response = 'error'
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.create-account-image {
|
||||
width: 50%;
|
||||
max-width: 200px;
|
||||
}
|
||||
</style>
|
||||
39
webapp/components/Registration/EmailDisplayAndVerify.vue
Normal file
39
webapp/components/Registration/EmailDisplayAndVerify.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<ds-text>
|
||||
{{ $t('components.registration.email-display.yourEmail') }}
|
||||
<b v-if="emailAsString.length > 0">
|
||||
{{ emailAsString }}
|
||||
<b v-if="!isEmailFormat" class="email-warning">
|
||||
{{ $t('components.registration.email-display.warningFormat') }}
|
||||
</b>
|
||||
</b>
|
||||
<b v-else class="email-warning">
|
||||
{{ $t('components.registration.email-display.warningUndef') }}
|
||||
</b>
|
||||
</ds-text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEmail } from 'validator'
|
||||
|
||||
export default {
|
||||
name: 'EmailDisplayAndVerify',
|
||||
props: {
|
||||
email: { type: String, default: () => '' },
|
||||
},
|
||||
computed: {
|
||||
isEmailFormat() {
|
||||
return isEmail(this.email)
|
||||
},
|
||||
emailAsString() {
|
||||
return !this.email ? '' : this.email
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.email-warning {
|
||||
color: $text-color-danger;
|
||||
}
|
||||
</style>
|
||||
@ -1,262 +0,0 @@
|
||||
<template>
|
||||
<!-- Wolle <ds-space v-if="!data && !error" margin="large"> -->
|
||||
<ds-form
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
>
|
||||
<!-- Wolle <h1>
|
||||
{{
|
||||
invitation
|
||||
? $t('profile.invites.title', metadata)
|
||||
: $t('components.registration.signup.title', metadata)
|
||||
}}
|
||||
</h1> -->
|
||||
<ds-text
|
||||
v-if="token"
|
||||
v-html="$t('registration.signup.form.invitation-code', { code: token })"
|
||||
/>
|
||||
<ds-text>
|
||||
{{
|
||||
invitation
|
||||
? $t('profile.invites.description')
|
||||
: $t('components.registration.signup.form.description')
|
||||
}}
|
||||
</ds-text>
|
||||
<ds-input
|
||||
:placeholder="invitation ? $t('profile.invites.emailPlaceholder') : $t('login.email')"
|
||||
type="email"
|
||||
id="email"
|
||||
model="email"
|
||||
name="email"
|
||||
icon="envelope"
|
||||
/>
|
||||
<slot></slot>
|
||||
<ds-text v-if="sliderData.collectedInputData.emailSend">
|
||||
<input id="checkbox" type="checkbox" v-model="sendEmailAgain" :checked="sendEmailAgain" />
|
||||
<label for="checkbox0">
|
||||
<!-- Wolle {{ $t('termsAndConditions.termsAndConditionsConfirmed') }} -->
|
||||
{{ 'Send e-mail again' }}
|
||||
</label>
|
||||
</ds-text>
|
||||
</ds-form>
|
||||
<!-- Wolle </ds-space>
|
||||
<div v-else margin="large">
|
||||
<template v-if="!error">
|
||||
<transition name="ds-transition-fade">
|
||||
<sweetalert-icon icon="info" />
|
||||
</transition>
|
||||
<ds-text align="center" v-html="submitMessage" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<transition name="ds-transition-fade">
|
||||
<sweetalert-icon icon="error" />
|
||||
</transition>
|
||||
<ds-text align="center">{{ error.message }}</ds-text>
|
||||
<ds-space centered class="space-top">
|
||||
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
|
||||
</ds-space>
|
||||
</template>
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import metadata from '~/constants/metadata'
|
||||
import { isEmail } from 'validator'
|
||||
import normalizeEmail from '~/components/utils/NormalizeEmail'
|
||||
// Wolle import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
|
||||
export const SignupMutation = gql`
|
||||
mutation($email: String!) {
|
||||
Signup(email: $email) {
|
||||
email
|
||||
}
|
||||
}
|
||||
`
|
||||
export const SignupByInvitationMutation = gql`
|
||||
mutation($email: String!, $token: String!) {
|
||||
SignupByInvitation(email: $email, token: $token) {
|
||||
email
|
||||
}
|
||||
}
|
||||
`
|
||||
export default {
|
||||
name: 'RegistrationItemEnterEmail',
|
||||
components: {
|
||||
// Wolle SweetalertIcon,
|
||||
},
|
||||
props: {
|
||||
sliderData: { type: Object, required: true },
|
||||
token: { type: String, default: null }, // Wolle not used???
|
||||
invitation: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
metadata,
|
||||
formData: {
|
||||
email: '',
|
||||
},
|
||||
formSchema: {
|
||||
email: {
|
||||
type: 'email',
|
||||
required: true,
|
||||
message: this.$t('common.validations.email'),
|
||||
},
|
||||
},
|
||||
// TODO: Our styleguide does not support checkmarks.
|
||||
// Integrate termsAndConditionsConfirmed into `this.formData` once we
|
||||
// have checkmarks available.
|
||||
sendEmailAgain: false,
|
||||
error: null, // Wolle
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
// Code that will run only after the entire view has been rendered
|
||||
this.formData.email = this.sliderData.collectedInputData.email
|
||||
? this.sliderData.collectedInputData.email
|
||||
: ''
|
||||
this.sendValidation()
|
||||
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
sliderSettings: {
|
||||
...this.buttonValues().sliderSettings,
|
||||
buttonSliderCallback: this.onNextClick,
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
sendEmailAgain() {
|
||||
this.setButtonValues()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
sliderIndex() {
|
||||
return this.sliderData.sliderIndex // to have a shorter notation
|
||||
},
|
||||
// Wolle submitMessage() {
|
||||
// const { email } = this.data.Signup
|
||||
// return this.$t('components.registration.signup.form.success', { email })
|
||||
// },
|
||||
validInput() {
|
||||
return isEmail(this.formData.email)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async sendValidation() {
|
||||
if (this.formData.email && isEmail(this.formData.email)) {
|
||||
this.formData.email = normalizeEmail(this.formData.email)
|
||||
}
|
||||
const { email } = this.formData
|
||||
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, { collectedInputData: { email } })
|
||||
},
|
||||
async handleInput() {
|
||||
this.sendValidation()
|
||||
},
|
||||
async handleInputValid() {
|
||||
this.sendValidation()
|
||||
},
|
||||
buttonValues() {
|
||||
return {
|
||||
sliderSettings: {
|
||||
buttonTitle: this.sliderData.collectedInputData.emailSend
|
||||
? this.sendEmailAgain
|
||||
? 'Resend e-mail'
|
||||
: 'Skip resend'
|
||||
: 'Send e-mail', // Wolle
|
||||
buttonIcon: this.sliderData.collectedInputData.emailSend
|
||||
? this.sendEmailAgain
|
||||
? 'envelope'
|
||||
: 'arrow-right'
|
||||
: 'envelope', // Wolle
|
||||
},
|
||||
}
|
||||
},
|
||||
setButtonValues() {
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, this.buttonValues())
|
||||
},
|
||||
async onNextClick() {
|
||||
const mutation = this.token ? SignupByInvitationMutation : SignupMutation
|
||||
const { token } = this
|
||||
const { email } = this.formData
|
||||
const variables = { email, token }
|
||||
|
||||
if (!this.sendEmailAgain && this.sliderData.collectedInputData.emailSend) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
this.sendEmailAgain ||
|
||||
!this.sliderData.sliders[this.sliderIndex].data.request ||
|
||||
(this.sliderData.sliders[this.sliderIndex].data.request &&
|
||||
(!this.sliderData.sliders[this.sliderIndex].data.request.variables ||
|
||||
(this.sliderData.sliders[this.sliderIndex].data.request.variables &&
|
||||
!this.sliderData.sliders[this.sliderIndex].data.request.variables === variables)))
|
||||
) {
|
||||
this.sliderData.setSliderValuesCallback(
|
||||
this.sliderData.sliders[this.sliderIndex].validated,
|
||||
{ sliderData: { request: { variables }, response: null } },
|
||||
)
|
||||
|
||||
try {
|
||||
const response = await this.$apollo.mutate({ mutation, variables }) // e-mail is send in emailMiddleware of backend
|
||||
this.sliderData.setSliderValuesCallback(
|
||||
this.sliderData.sliders[this.sliderIndex].validated,
|
||||
{ sliderData: { response: response.data } },
|
||||
)
|
||||
|
||||
if (this.sliderData.sliders[this.sliderIndex].data.response) {
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
collectedInputData: { emailSend: true },
|
||||
})
|
||||
this.setButtonValues()
|
||||
|
||||
const { email: respnseEmail } =
|
||||
this.sliderData.sliders[this.sliderIndex].data.response.Signup ||
|
||||
this.sliderData.sliders[this.sliderIndex].data.response.SignupByInvitation
|
||||
this.$toast.success(
|
||||
this.$t('components.registration.email.form.success', { email: respnseEmail }),
|
||||
)
|
||||
}
|
||||
return true
|
||||
} catch (err) {
|
||||
this.sliderData.setSliderValuesCallback(
|
||||
this.sliderData.sliders[this.sliderIndex].validated,
|
||||
{ sliderData: { request: null, response: null } },
|
||||
)
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
collectedInputData: { emailSend: false },
|
||||
})
|
||||
this.setButtonValues()
|
||||
|
||||
const { message } = err
|
||||
const mapping = {
|
||||
'A user account with this email already exists': 'email-exists',
|
||||
'Invitation code already used or does not exist': 'invalid-invitation-token',
|
||||
}
|
||||
for (const [pattern, key] of Object.entries(mapping)) {
|
||||
if (message.includes(pattern))
|
||||
this.error = {
|
||||
key,
|
||||
message: this.$t(`components.registration.signup.form.errors.${key}`),
|
||||
}
|
||||
}
|
||||
if (!this.error) {
|
||||
this.$toast.error(message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.space-top {
|
||||
margin-top: 6ex;
|
||||
}
|
||||
</style>
|
||||
@ -1,107 +0,0 @@
|
||||
<template>
|
||||
<ds-form
|
||||
class="enter-nonce"
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="handleSubmitVerify"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
>
|
||||
<ds-text>
|
||||
<!-- Wolle {{ $t('components.enter-nonce.form.description') }} -->
|
||||
Your e-mail address:
|
||||
<b>{{ this.sliderData.collectedInputData.email }}</b>
|
||||
</ds-text>
|
||||
<ds-input
|
||||
:placeholder="$t('components.enter-nonce.form.nonce')"
|
||||
model="nonce"
|
||||
name="nonce"
|
||||
id="nonce"
|
||||
icon="question-circle"
|
||||
/>
|
||||
<ds-text>
|
||||
{{ $t('components.enter-nonce.form.description') }}
|
||||
</ds-text>
|
||||
<slot></slot>
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RegistrationItemEnterNonce',
|
||||
props: {
|
||||
sliderData: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
nonce: '',
|
||||
},
|
||||
formSchema: {
|
||||
nonce: {
|
||||
type: 'string',
|
||||
// Wolle min: 6,
|
||||
// max: 6,
|
||||
required: true,
|
||||
message: this.$t('components.enter-nonce.form.validations.length'),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
// Code that will run only after the entire view has been rendered
|
||||
// console.log('mounted !!! ')
|
||||
this.formData.nonce = this.sliderData.collectedInputData.nonce
|
||||
? this.sliderData.collectedInputData.nonce
|
||||
: ''
|
||||
this.sendValidation()
|
||||
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
sliderSettings: { buttonSliderCallback: this.onNextClick },
|
||||
})
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
validInput() {
|
||||
return this.formData.nonce.length === 6
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
sendValidation() {
|
||||
const { nonce } = this.formData
|
||||
|
||||
// Wolle shall the nonce be validated in the database?
|
||||
// let dbValidated = false
|
||||
// if (this.validInput) {
|
||||
// await this.handleSubmitVerify()
|
||||
// dbValidated = this.sliderData.sliders[this.sliderIndex].data.response.isValidInviteCode
|
||||
// }
|
||||
// this.sliderData.setSliderValuesCallback(dbValidated, {
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
collectedInputData: {
|
||||
nonce,
|
||||
},
|
||||
})
|
||||
},
|
||||
async handleInput() {
|
||||
this.sendValidation()
|
||||
},
|
||||
async handleInputValid() {
|
||||
this.sendValidation()
|
||||
},
|
||||
handleSubmitVerify() {},
|
||||
onNextClick() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.enter-nonce {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: $space-large 0 $space-xxx-small 0;
|
||||
}
|
||||
</style>
|
||||
@ -23,17 +23,10 @@
|
||||
</ds-space>
|
||||
</div>
|
||||
<div v-else class="create-account-card">
|
||||
<!-- Wolle <ds-space margin-top="large">
|
||||
<ds-heading size="h3">
|
||||
{{ $t('components.registration.create-user-account.title') }}
|
||||
</ds-heading>
|
||||
</ds-space> -->
|
||||
|
||||
<ds-form
|
||||
class="create-user-account"
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="submit"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
>
|
||||
@ -47,14 +40,6 @@
|
||||
:label="$t('settings.data.labelName')"
|
||||
:placeholder="$t('settings.data.namePlaceholder')"
|
||||
/>
|
||||
<ds-input
|
||||
id="about"
|
||||
model="about"
|
||||
type="textarea"
|
||||
rows="3"
|
||||
:label="$t('settings.data.labelBio')"
|
||||
:placeholder="$t('settings.data.labelBio')"
|
||||
/>
|
||||
<ds-input
|
||||
id="password"
|
||||
model="password"
|
||||
@ -71,11 +56,7 @@
|
||||
/>
|
||||
<password-strength class="password-strength" :password="formData.password" />
|
||||
|
||||
<ds-text>
|
||||
<!-- Wolle {{ $t('components.enter-nonce.form.description') }} -->
|
||||
Your e-mail address:
|
||||
<b>{{ this.sliderData.collectedInputData.email }}</b>
|
||||
</ds-text>
|
||||
<email-display-and-verify :email="sliderData.collectedInputData.email" />
|
||||
|
||||
<ds-text>
|
||||
<input
|
||||
@ -85,41 +66,31 @@
|
||||
:checked="termsAndConditionsConfirmed"
|
||||
/>
|
||||
<label for="checkbox0">
|
||||
{{ $t('termsAndConditions.termsAndConditionsConfirmed') }}
|
||||
{{ $t('components.registration.create-user-account.termsAndCondsEtcConfirmed') }}
|
||||
<br />
|
||||
<nuxt-link to="/terms-and-conditions">{{ $t('site.termsAndConditions') }}</nuxt-link>
|
||||
</label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox1" type="checkbox" v-model="dataPrivacy" :checked="dataPrivacy" />
|
||||
<label for="checkbox1">
|
||||
{{ $t('components.registration.signup.form.data-privacy') }}
|
||||
<a :href="'/terms-and-conditions'" target="_blank">
|
||||
{{ $t('site.termsAndConditions') }}
|
||||
</a>
|
||||
<br />
|
||||
<nuxt-link to="/data-privacy">
|
||||
<a :href="'/data-privacy'" target="_blank">
|
||||
{{ $t('site.data-privacy') }}
|
||||
</nuxt-link>
|
||||
</a>
|
||||
</label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox2" type="checkbox" v-model="minimumAge" :checked="minimumAge" />
|
||||
<label
|
||||
for="checkbox2"
|
||||
v-html="$t('components.registration.signup.form.minimum-age')"
|
||||
></label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox3" type="checkbox" v-model="noCommercial" :checked="noCommercial" />
|
||||
<label
|
||||
for="checkbox3"
|
||||
v-html="$t('components.registration.signup.form.no-commercial')"
|
||||
></label>
|
||||
</ds-text>
|
||||
<ds-text>
|
||||
<input id="checkbox4" type="checkbox" v-model="noPolitical" :checked="noPolitical" />
|
||||
<label
|
||||
for="checkbox4"
|
||||
v-html="$t('components.registration.signup.form.no-political')"
|
||||
></label>
|
||||
<input
|
||||
id="checkbox1"
|
||||
type="checkbox"
|
||||
v-model="recieveCommunicationAsEmailsEtcConfirmed"
|
||||
:checked="recieveCommunicationAsEmailsEtcConfirmed"
|
||||
/>
|
||||
<label for="checkbox1">
|
||||
{{
|
||||
$t(
|
||||
'components.registration.create-user-account.recieveCommunicationAsEmailsEtcConfirmed',
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</ds-text>
|
||||
</template>
|
||||
</ds-form>
|
||||
@ -130,15 +101,17 @@
|
||||
import { VERSION } from '~/constants/terms-and-conditions-version.js'
|
||||
import links from '~/constants/links'
|
||||
import emails from '~/constants/emails'
|
||||
import PasswordStrength from '../Password/Strength'
|
||||
import PasswordStrength from '~/components/Password/Strength'
|
||||
import EmailDisplayAndVerify from './EmailDisplayAndVerify'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
import PasswordForm from '~/components/utils/PasswordFormHelper'
|
||||
import { SignupVerificationMutation } from '~/graphql/Registration.js'
|
||||
|
||||
export default {
|
||||
name: 'RegistrationItemCreateUserAccount',
|
||||
name: 'RegistrationSlideCreate',
|
||||
components: {
|
||||
PasswordStrength,
|
||||
EmailDisplayAndVerify,
|
||||
SweetalertIcon,
|
||||
},
|
||||
props: {
|
||||
@ -151,7 +124,6 @@ export default {
|
||||
supportEmail: emails.SUPPORT,
|
||||
formData: {
|
||||
name: '',
|
||||
about: '',
|
||||
...passwordForm.formData,
|
||||
},
|
||||
formSchema: {
|
||||
@ -160,32 +132,23 @@ export default {
|
||||
required: true,
|
||||
min: 3,
|
||||
},
|
||||
about: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
...passwordForm.formSchema,
|
||||
},
|
||||
response: null, // Wolle
|
||||
response: null,
|
||||
// TODO: Our styleguide does not support checkmarks.
|
||||
// Integrate termsAndConditionsConfirmed into `this.formData` once we
|
||||
// have checkmarks available.
|
||||
termsAndConditionsConfirmed: false,
|
||||
dataPrivacy: false,
|
||||
minimumAge: false,
|
||||
noCommercial: false,
|
||||
noPolitical: false,
|
||||
recieveCommunicationAsEmailsEtcConfirmed: false,
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
// Code that will run only after the entire view has been rendered
|
||||
|
||||
this.formData.name = this.sliderData.collectedInputData.name
|
||||
? this.sliderData.collectedInputData.name
|
||||
: ''
|
||||
this.formData.about = this.sliderData.collectedInputData.about
|
||||
? this.sliderData.collectedInputData.about
|
||||
: ''
|
||||
this.formData.password = this.sliderData.collectedInputData.password
|
||||
? this.sliderData.collectedInputData.password
|
||||
: ''
|
||||
@ -196,17 +159,9 @@ export default {
|
||||
.termsAndConditionsConfirmed
|
||||
? this.sliderData.collectedInputData.termsAndConditionsConfirmed
|
||||
: false
|
||||
this.dataPrivacy = this.sliderData.collectedInputData.dataPrivacy
|
||||
? this.sliderData.collectedInputData.dataPrivacy
|
||||
: false
|
||||
this.minimumAge = this.sliderData.collectedInputData.minimumAge
|
||||
? this.sliderData.collectedInputData.minimumAge
|
||||
: false
|
||||
this.noCommercial = this.sliderData.collectedInputData.noCommercial
|
||||
? this.sliderData.collectedInputData.noCommercial
|
||||
: false
|
||||
this.noPolitical = this.sliderData.collectedInputData.noPolitical
|
||||
? this.sliderData.collectedInputData.noPolitical
|
||||
this.recieveCommunicationAsEmailsEtcConfirmed = this.sliderData.collectedInputData
|
||||
.recieveCommunicationAsEmailsEtcConfirmed
|
||||
? this.sliderData.collectedInputData.recieveCommunicationAsEmailsEtcConfirmed
|
||||
: false
|
||||
this.sendValidation()
|
||||
|
||||
@ -222,10 +177,7 @@ export default {
|
||||
this.formData.password.length >= 1 &&
|
||||
this.formData.password === this.formData.passwordConfirmation &&
|
||||
this.termsAndConditionsConfirmed &&
|
||||
this.dataPrivacy &&
|
||||
this.minimumAge &&
|
||||
this.noCommercial &&
|
||||
this.noPolitical
|
||||
this.recieveCommunicationAsEmailsEtcConfirmed
|
||||
)
|
||||
},
|
||||
},
|
||||
@ -233,41 +185,22 @@ export default {
|
||||
termsAndConditionsConfirmed() {
|
||||
this.sendValidation()
|
||||
},
|
||||
dataPrivacy() {
|
||||
this.sendValidation()
|
||||
},
|
||||
minimumAge() {
|
||||
this.sendValidation()
|
||||
},
|
||||
noCommercial() {
|
||||
this.sendValidation()
|
||||
},
|
||||
noPolitical() {
|
||||
recieveCommunicationAsEmailsEtcConfirmed() {
|
||||
this.sendValidation()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
sendValidation() {
|
||||
const { name, about, password, passwordConfirmation } = this.formData
|
||||
const {
|
||||
termsAndConditionsConfirmed,
|
||||
dataPrivacy,
|
||||
minimumAge,
|
||||
noCommercial,
|
||||
noPolitical,
|
||||
} = this
|
||||
const { name, password, passwordConfirmation } = this.formData
|
||||
const { termsAndConditionsConfirmed, recieveCommunicationAsEmailsEtcConfirmed } = this
|
||||
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
collectedInputData: {
|
||||
name,
|
||||
about,
|
||||
password,
|
||||
passwordConfirmation,
|
||||
termsAndConditionsConfirmed,
|
||||
dataPrivacy,
|
||||
minimumAge,
|
||||
noCommercial,
|
||||
noPolitical,
|
||||
recieveCommunicationAsEmailsEtcConfirmed,
|
||||
},
|
||||
})
|
||||
},
|
||||
@ -278,31 +211,39 @@ export default {
|
||||
this.sendValidation()
|
||||
},
|
||||
async submit() {
|
||||
const { name, password, about } = this.formData
|
||||
const { email, nonce } = this.sliderData.collectedInputData
|
||||
const { name, password } = this.formData
|
||||
const { email, inviteCode = null, nonce } = this.sliderData.collectedInputData
|
||||
const termsAndConditionsAgreedVersion = VERSION
|
||||
const locale = this.$i18n.locale()
|
||||
try {
|
||||
this.sliderData.setSliderValuesCallback(null, {
|
||||
sliderSettings: { buttonLoading: true },
|
||||
})
|
||||
await this.$apollo.mutate({
|
||||
mutation: SignupVerificationMutation,
|
||||
variables: {
|
||||
name,
|
||||
password,
|
||||
about,
|
||||
email,
|
||||
inviteCode,
|
||||
nonce,
|
||||
termsAndConditionsAgreedVersion,
|
||||
locale,
|
||||
},
|
||||
})
|
||||
this.response = 'success'
|
||||
// Wolle setTimeout(() => {
|
||||
// this.$emit('userCreated', {
|
||||
// email,
|
||||
// password,
|
||||
// })
|
||||
// }, 3000)
|
||||
setTimeout(async () => {
|
||||
await this.$store.dispatch('auth/login', { email, password })
|
||||
this.$toast.success(this.$t('login.success'))
|
||||
this.$router.push('/')
|
||||
this.sliderData.setSliderValuesCallback(null, {
|
||||
sliderSettings: { buttonLoading: false },
|
||||
})
|
||||
}, 3000)
|
||||
} catch (err) {
|
||||
this.sliderData.setSliderValuesCallback(null, {
|
||||
sliderSettings: { buttonLoading: false },
|
||||
})
|
||||
this.response = 'error'
|
||||
}
|
||||
},
|
||||
208
webapp/components/Registration/RegistrationSlideEmail.vue
Normal file
208
webapp/components/Registration/RegistrationSlideEmail.vue
Normal file
@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<ds-form
|
||||
class="enter-email"
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
>
|
||||
<ds-text>
|
||||
{{ $t('components.registration.signup.form.description') }}
|
||||
</ds-text>
|
||||
<ds-input
|
||||
:placeholder="$t('login.email')"
|
||||
type="email"
|
||||
id="email"
|
||||
model="email"
|
||||
name="email"
|
||||
icon="envelope"
|
||||
/>
|
||||
<slot></slot>
|
||||
<ds-text v-if="sliderData.collectedInputData.emailSend">
|
||||
<input id="checkbox" type="checkbox" v-model="sendEmailAgain" :checked="sendEmailAgain" />
|
||||
<label for="checkbox0">
|
||||
{{ $t('components.registration.email.form.sendEmailAgain') }}
|
||||
</label>
|
||||
</ds-text>
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import metadata from '~/constants/metadata'
|
||||
import { isEmail } from 'validator'
|
||||
import normalizeEmail from '~/components/utils/NormalizeEmail'
|
||||
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
|
||||
|
||||
export const SignupMutation = gql`
|
||||
mutation($email: String!, $inviteCode: String) {
|
||||
Signup(email: $email, inviteCode: $inviteCode) {
|
||||
email
|
||||
}
|
||||
}
|
||||
`
|
||||
export default {
|
||||
name: 'RegistrationSlideEmail',
|
||||
props: {
|
||||
sliderData: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
metadata,
|
||||
formData: {
|
||||
email: '',
|
||||
},
|
||||
formSchema: {
|
||||
email: {
|
||||
type: 'email',
|
||||
required: true,
|
||||
message: this.$t('common.validations.email'),
|
||||
},
|
||||
},
|
||||
// TODO: Our styleguide does not support checkmarks.
|
||||
// Integrate termsAndConditionsConfirmed into `this.formData` once we
|
||||
// have checkmarks available.
|
||||
sendEmailAgain: false,
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
// Code that will run only after the entire view has been rendered
|
||||
|
||||
this.formData.email = this.sliderData.collectedInputData.email
|
||||
? this.sliderData.collectedInputData.email
|
||||
: ''
|
||||
this.sendValidation()
|
||||
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
sliderSettings: {
|
||||
...this.buttonValues().sliderSettings,
|
||||
buttonSliderCallback: this.onNextClick,
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
sendEmailAgain() {
|
||||
this.setButtonValues()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
sliderIndex() {
|
||||
return this.sliderData.sliderIndex // to have a shorter notation
|
||||
},
|
||||
validInput() {
|
||||
return isEmail(this.formData.email)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async sendValidation() {
|
||||
if (this.formData.email && isEmail(this.formData.email)) {
|
||||
this.formData.email = normalizeEmail(this.formData.email)
|
||||
}
|
||||
const { email } = this.formData
|
||||
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, { collectedInputData: { email } })
|
||||
},
|
||||
async handleInput() {
|
||||
this.sendValidation()
|
||||
},
|
||||
async handleInputValid() {
|
||||
this.sendValidation()
|
||||
},
|
||||
buttonValues() {
|
||||
return {
|
||||
sliderSettings: {
|
||||
buttonTitleIdent: this.sliderData.collectedInputData.emailSend
|
||||
? this.sendEmailAgain
|
||||
? 'components.registration.email.buttonTitle.resend'
|
||||
: 'components.registration.email.buttonTitle.skipResend'
|
||||
: 'components.registration.email.buttonTitle.send',
|
||||
buttonIcon: this.sliderData.collectedInputData.emailSend
|
||||
? this.sendEmailAgain
|
||||
? 'envelope'
|
||||
: 'arrow-right'
|
||||
: 'envelope',
|
||||
},
|
||||
}
|
||||
},
|
||||
setButtonValues() {
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, this.buttonValues())
|
||||
},
|
||||
isVariablesRequested(variables) {
|
||||
return (
|
||||
this.sliderData.sliders[this.sliderIndex].data.request &&
|
||||
this.sliderData.sliders[this.sliderIndex].data.request.variables &&
|
||||
this.sliderData.sliders[this.sliderIndex].data.request.variables.email === variables.email
|
||||
)
|
||||
},
|
||||
async onNextClick() {
|
||||
const { email } = this.formData
|
||||
const { inviteCode = null } = this.sliderData.collectedInputData
|
||||
const variables = { email, inviteCode }
|
||||
|
||||
if (this.sliderData.collectedInputData.emailSend && !this.sendEmailAgain) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
!this.sliderData.collectedInputData.emailSend ||
|
||||
this.sendEmailAgain ||
|
||||
!this.isVariablesRequested(variables)
|
||||
) {
|
||||
try {
|
||||
this.sliderData.setSliderValuesCallback(null, {
|
||||
sliderSettings: { buttonLoading: true },
|
||||
})
|
||||
const response = await this.$apollo.mutate({ mutation: SignupMutation, variables }) // e-mail is send in emailMiddleware of backend
|
||||
this.sliderData.setSliderValuesCallback(null, {
|
||||
sliderData: { request: { variables }, response: response.data },
|
||||
})
|
||||
|
||||
if (this.sliderData.sliders[this.sliderIndex].data.response) {
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
collectedInputData: { emailSend: true },
|
||||
})
|
||||
this.setButtonValues()
|
||||
|
||||
const { email: responseEmail } = this.sliderData.sliders[
|
||||
this.sliderIndex
|
||||
].data.response.Signup
|
||||
this.$toast.success(
|
||||
this.$t('components.registration.email.form.success', { email: responseEmail }),
|
||||
)
|
||||
}
|
||||
this.sliderData.setSliderValuesCallback(null, {
|
||||
sliderSettings: { buttonLoading: false },
|
||||
})
|
||||
return true
|
||||
} catch (err) {
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
sliderData: { request: null, response: null },
|
||||
collectedInputData: { emailSend: false },
|
||||
sliderSettings: { buttonLoading: false },
|
||||
})
|
||||
this.setButtonValues()
|
||||
|
||||
this.$toast.error(
|
||||
translateErrorMessage(
|
||||
err.message,
|
||||
{
|
||||
'A user account with this email already exists':
|
||||
'components.registration.signup.form.errors.email-exists',
|
||||
},
|
||||
this.$t,
|
||||
),
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.space-top {
|
||||
margin-top: 6ex;
|
||||
}
|
||||
</style>
|
||||
@ -7,14 +7,14 @@
|
||||
@input-valid="handleInputValid"
|
||||
>
|
||||
<ds-input
|
||||
:placeholder="$t('components.enter-invite.form.invite-code')"
|
||||
:placeholder="$t('components.registration.invite-code.form.invite-code')"
|
||||
model="inviteCode"
|
||||
name="inviteCode"
|
||||
id="inviteCode"
|
||||
icon="question-circle"
|
||||
/>
|
||||
<ds-text>
|
||||
{{ $t('components.enter-invite.form.description') }}
|
||||
{{ $t('components.registration.invite-code.form.description') }}
|
||||
</ds-text>
|
||||
<slot></slot>
|
||||
</ds-form>
|
||||
@ -29,7 +29,7 @@ export const isValidInviteCodeQuery = gql`
|
||||
}
|
||||
`
|
||||
export default {
|
||||
name: 'RegistrationItemEnterInvite',
|
||||
name: 'RegistrationSlideInvite',
|
||||
props: {
|
||||
sliderData: { type: Object, required: true },
|
||||
},
|
||||
@ -41,17 +41,19 @@ export default {
|
||||
formSchema: {
|
||||
inviteCode: {
|
||||
type: 'string',
|
||||
// Wolle min: 6,
|
||||
// max: 6,
|
||||
min: 6,
|
||||
max: 6,
|
||||
required: true,
|
||||
message: this.$t('components.enter-invite.form.validations.length'),
|
||||
message: this.$t('components.registration.invite-code.form.validations.length'),
|
||||
},
|
||||
},
|
||||
dbRequestInProgress: false,
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
// Code that will run only after the entire view has been rendered
|
||||
|
||||
this.formData.inviteCode = this.sliderData.collectedInputData.inviteCode
|
||||
? this.sliderData.collectedInputData.inviteCode
|
||||
: ''
|
||||
@ -74,12 +76,14 @@ export default {
|
||||
async sendValidation() {
|
||||
const { inviteCode } = this.formData
|
||||
|
||||
this.sliderData.setSliderValuesCallback(null, { collectedInputData: { inviteCode } })
|
||||
|
||||
let dbValidated = false
|
||||
if (this.validInput) {
|
||||
await this.handleSubmitVerify()
|
||||
dbValidated = this.sliderData.sliders[this.sliderIndex].data.response.isValidInviteCode
|
||||
}
|
||||
this.sliderData.setSliderValuesCallback(dbValidated, { collectedInputData: { inviteCode } })
|
||||
this.sliderData.setSliderValuesCallback(dbValidated)
|
||||
},
|
||||
async handleInput() {
|
||||
this.sendValidation()
|
||||
@ -87,45 +91,53 @@ export default {
|
||||
async handleInputValid() {
|
||||
this.sendValidation()
|
||||
},
|
||||
isVariablesRequested(variables) {
|
||||
return (
|
||||
this.sliderData.sliders[this.sliderIndex].data.request &&
|
||||
this.sliderData.sliders[this.sliderIndex].data.request.variables &&
|
||||
this.sliderData.sliders[this.sliderIndex].data.request.variables.code === variables.code
|
||||
)
|
||||
},
|
||||
async handleSubmitVerify() {
|
||||
const { inviteCode } = this.formData
|
||||
const { inviteCode } = this.sliderData.collectedInputData
|
||||
const variables = { code: inviteCode }
|
||||
|
||||
if (
|
||||
!this.sliderData.sliders[this.sliderIndex].data.request ||
|
||||
(this.sliderData.sliders[this.sliderIndex].data.request &&
|
||||
(!this.sliderData.sliders[this.sliderIndex].data.request.variables ||
|
||||
(this.sliderData.sliders[this.sliderIndex].data.request.variables &&
|
||||
!this.sliderData.sliders[this.sliderIndex].data.request.variables === variables)))
|
||||
) {
|
||||
this.sliderData.setSliderValuesCallback(
|
||||
this.sliderData.sliders[this.sliderIndex].validated,
|
||||
{ sliderData: { request: { variables }, response: null } },
|
||||
)
|
||||
|
||||
if (!this.isVariablesRequested(variables) && !this.dbRequestInProgress) {
|
||||
try {
|
||||
const response = await this.$apollo.query({ query: isValidInviteCodeQuery, variables })
|
||||
this.sliderData.setSliderValuesCallback(
|
||||
this.sliderData.sliders[this.sliderIndex].validated,
|
||||
{ sliderData: { response: response.data } },
|
||||
)
|
||||
this.dbRequestInProgress = true
|
||||
|
||||
if (
|
||||
this.sliderData.sliders[this.sliderIndex].data.response &&
|
||||
this.sliderData.sliders[this.sliderIndex].data.response.isValidInviteCode
|
||||
) {
|
||||
this.$toast.success(
|
||||
this.$t('components.registration.invite-code.form.success', { inviteCode }),
|
||||
)
|
||||
const response = await this.$apollo.query({ query: isValidInviteCodeQuery, variables })
|
||||
this.sliderData.setSliderValuesCallback(null, {
|
||||
sliderData: {
|
||||
request: { variables },
|
||||
response: response.data,
|
||||
},
|
||||
})
|
||||
|
||||
if (this.sliderData.sliders[this.sliderIndex].data.response) {
|
||||
if (this.sliderData.sliders[this.sliderIndex].data.response.isValidInviteCode) {
|
||||
this.$toast.success(
|
||||
this.$t('components.registration.invite-code.form.validations.success', {
|
||||
inviteCode,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
this.$toast.error(
|
||||
this.$t('components.registration.invite-code.form.validations.error', {
|
||||
inviteCode,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.sliderData.setSliderValuesCallback(
|
||||
this.sliderData.sliders[this.sliderIndex].validated,
|
||||
{ sliderData: { response: { isValidInviteCode: false } } },
|
||||
)
|
||||
this.sliderData.setSliderValuesCallback(false, {
|
||||
sliderData: { response: { isValidInviteCode: false } },
|
||||
})
|
||||
|
||||
const { message } = err
|
||||
this.$toast.error(message)
|
||||
} finally {
|
||||
this.dbRequestInProgress = false
|
||||
}
|
||||
}
|
||||
},
|
||||
35
webapp/components/Registration/RegistrationSlideNoPublic.vue
Normal file
35
webapp/components/Registration/RegistrationSlideNoPublic.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<ds-space centered>
|
||||
<hc-empty icon="events" :message="$t('components.registration.signup.unavailable')" />
|
||||
<slot></slot>
|
||||
</ds-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HcEmpty from '~/components/Empty/Empty'
|
||||
|
||||
export default {
|
||||
name: 'RegistrationSlideNoPublic',
|
||||
components: {
|
||||
HcEmpty,
|
||||
},
|
||||
props: {
|
||||
sliderData: { type: Object, required: true },
|
||||
},
|
||||
mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
// Code that will run only after the entire view has been rendered
|
||||
|
||||
this.sliderData.setSliderValuesCallback(true, {
|
||||
sliderSettings: { buttonSliderCallback: this.onNextClick },
|
||||
})
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onNextClick() {
|
||||
this.$router.history.push('/login')
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
168
webapp/components/Registration/RegistrationSlideNonce.vue
Normal file
168
webapp/components/Registration/RegistrationSlideNonce.vue
Normal file
@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<ds-form
|
||||
class="enter-nonce"
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="handleSubmitVerify"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
>
|
||||
<email-display-and-verify :email="sliderData.collectedInputData.email" />
|
||||
<ds-input
|
||||
:placeholder="$t('components.registration.email-nonce.form.nonce')"
|
||||
model="nonce"
|
||||
name="nonce"
|
||||
id="nonce"
|
||||
icon="question-circle"
|
||||
/>
|
||||
<ds-text>
|
||||
{{ $t('components.registration.email-nonce.form.description') }}
|
||||
</ds-text>
|
||||
<slot></slot>
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { isEmail } from 'validator'
|
||||
import EmailDisplayAndVerify from './EmailDisplayAndVerify'
|
||||
|
||||
export const verifyNonceQuery = gql`
|
||||
query($email: String!, $nonce: String!) {
|
||||
VerifyNonce(email: $email, nonce: $nonce)
|
||||
}
|
||||
`
|
||||
export default {
|
||||
name: 'RegistrationSlideNonce',
|
||||
components: {
|
||||
EmailDisplayAndVerify,
|
||||
},
|
||||
props: {
|
||||
sliderData: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
nonce: '',
|
||||
},
|
||||
formSchema: {
|
||||
nonce: {
|
||||
type: 'string',
|
||||
min: 5,
|
||||
max: 5,
|
||||
required: true,
|
||||
message: this.$t('components.registration.email-nonce.form.validations.length'),
|
||||
},
|
||||
},
|
||||
dbRequestInProgress: false,
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
// Code that will run only after the entire view has been rendered
|
||||
|
||||
this.formData.nonce = this.sliderData.collectedInputData.nonce
|
||||
? this.sliderData.collectedInputData.nonce
|
||||
: ''
|
||||
this.sendValidation()
|
||||
|
||||
this.sliderData.setSliderValuesCallback(this.validInput, {
|
||||
sliderSettings: { buttonSliderCallback: this.onNextClick },
|
||||
})
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
sliderIndex() {
|
||||
return this.sliderData.sliderIndex // to have a shorter notation
|
||||
},
|
||||
validInput() {
|
||||
return this.formData.nonce.length === 5
|
||||
},
|
||||
isEmailFormat() {
|
||||
return isEmail(this.sliderData.collectedInputData.email)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async sendValidation() {
|
||||
const { nonce } = this.formData
|
||||
|
||||
this.sliderData.setSliderValuesCallback(null, { collectedInputData: { nonce } })
|
||||
|
||||
let dbValidated = false
|
||||
if (this.validInput) {
|
||||
await this.handleSubmitVerify()
|
||||
dbValidated = this.sliderData.sliders[this.sliderIndex].data.response.VerifyNonce
|
||||
}
|
||||
this.sliderData.setSliderValuesCallback(dbValidated)
|
||||
},
|
||||
async handleInput() {
|
||||
this.sendValidation()
|
||||
},
|
||||
async handleInputValid() {
|
||||
this.sendValidation()
|
||||
},
|
||||
isVariablesRequested(variables) {
|
||||
return (
|
||||
this.sliderData.sliders[this.sliderIndex].data.request &&
|
||||
this.sliderData.sliders[this.sliderIndex].data.request.variables &&
|
||||
this.sliderData.sliders[this.sliderIndex].data.request.variables.email ===
|
||||
variables.email &&
|
||||
this.sliderData.sliders[this.sliderIndex].data.request.variables.nonce === variables.nonce
|
||||
)
|
||||
},
|
||||
async handleSubmitVerify() {
|
||||
const { email, nonce } = this.sliderData.collectedInputData
|
||||
const variables = { email, nonce }
|
||||
|
||||
if (!this.isVariablesRequested(variables) && !this.dbRequestInProgress) {
|
||||
try {
|
||||
this.dbRequestInProgress = true
|
||||
|
||||
const response = await this.$apollo.query({ query: verifyNonceQuery, variables })
|
||||
this.sliderData.setSliderValuesCallback(null, {
|
||||
sliderData: { request: { variables }, response: response.data },
|
||||
})
|
||||
|
||||
if (this.sliderData.sliders[this.sliderIndex].data.response) {
|
||||
if (this.sliderData.sliders[this.sliderIndex].data.response.VerifyNonce) {
|
||||
this.$toast.success(
|
||||
this.$t('components.registration.email-nonce.form.validations.success', {
|
||||
email,
|
||||
nonce,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
this.$toast.error(
|
||||
this.$t('components.registration.email-nonce.form.validations.error', {
|
||||
email,
|
||||
nonce,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.sliderData.setSliderValuesCallback(false, {
|
||||
sliderData: { response: { VerifyNonce: false } },
|
||||
})
|
||||
|
||||
const { message } = err
|
||||
this.$toast.error(message)
|
||||
} finally {
|
||||
this.dbRequestInProgress = false
|
||||
}
|
||||
}
|
||||
},
|
||||
onNextClick() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.enter-nonce {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: $space-large 0 $space-xxx-small 0;
|
||||
}
|
||||
</style>
|
||||
@ -1,8 +1,10 @@
|
||||
import { storiesOf } from '@storybook/vue'
|
||||
import { withA11y } from '@storybook/addon-a11y'
|
||||
import RegistrationSlider from './RegistrationSlider.vue'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import Vuex from 'vuex'
|
||||
import helpers from '~/storybook/helpers'
|
||||
import Vue from 'vue'
|
||||
import RegistrationSlider from './RegistrationSlider.vue'
|
||||
|
||||
const plugins = [
|
||||
(app = {}) => {
|
||||
@ -14,11 +16,8 @@ const plugins = [
|
||||
if (JSON.stringify(data).includes('Signup')) {
|
||||
return { data: { Signup: { email: data.variables.email } } }
|
||||
}
|
||||
if (JSON.stringify(data).includes('SignupByInvitation')) {
|
||||
return { data: { SignupByInvitation: { email: data.variables.email } } }
|
||||
}
|
||||
if (JSON.stringify(data).includes('SignupVerification')) {
|
||||
return { data: { SignupByInvitation: { ...data.variables } } }
|
||||
return { data: { SignupVerification: { ...data.variables } } }
|
||||
}
|
||||
throw new Error(`Mutation name not found!`)
|
||||
},
|
||||
@ -26,6 +25,9 @@ const plugins = [
|
||||
if (JSON.stringify(data).includes('isValidInviteCode')) {
|
||||
return { data: { isValidInviteCode: true } }
|
||||
}
|
||||
if (JSON.stringify(data).includes('VerifyNonce')) {
|
||||
return { data: { VerifyNonce: true } }
|
||||
}
|
||||
throw new Error(`Query name not found!`)
|
||||
},
|
||||
}
|
||||
@ -35,12 +37,51 @@ const plugins = [
|
||||
]
|
||||
helpers.init({ plugins })
|
||||
|
||||
const createStore = ({ loginSuccess }) => {
|
||||
return new Vuex.Store({
|
||||
modules: {
|
||||
auth: {
|
||||
namespaced: true,
|
||||
state: () => ({
|
||||
pending: false,
|
||||
}),
|
||||
mutations: {
|
||||
SET_PENDING(state, pending) {
|
||||
state.pending = pending
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
pending(state) {
|
||||
return !!state.pending
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async login({ commit, dispatch }, args) {
|
||||
action('Vuex action `auth/login`')(args)
|
||||
return new Promise((resolve, reject) => {
|
||||
commit('SET_PENDING', true)
|
||||
setTimeout(() => {
|
||||
commit('SET_PENDING', false)
|
||||
if (loginSuccess) {
|
||||
resolve(loginSuccess)
|
||||
} else {
|
||||
reject(new Error('Login unsuccessful'))
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
storiesOf('RegistrationSlider', module)
|
||||
.addDecorator(withA11y)
|
||||
.addDecorator(helpers.layout)
|
||||
.add('invite-code empty', () => ({
|
||||
components: { RegistrationSlider },
|
||||
store: helpers.store,
|
||||
store: createStore({ loginSuccess: true }),
|
||||
data: () => ({}),
|
||||
template: `
|
||||
<registration-slider registrationType="invite-code" />
|
||||
@ -48,23 +89,19 @@ storiesOf('RegistrationSlider', module)
|
||||
}))
|
||||
.add('invite-code with data', () => ({
|
||||
components: { RegistrationSlider },
|
||||
store: helpers.store,
|
||||
store: createStore({ loginSuccess: true }),
|
||||
data: () => ({
|
||||
overwriteSliderData: {
|
||||
collectedInputData: {
|
||||
inviteCode: 'IN1T6Y',
|
||||
inviteCode: 'INZTBY',
|
||||
email: 'wolle.huss@pjannto.com',
|
||||
emailSend: false,
|
||||
nonce: 'NTRSCZ',
|
||||
nonce: '47539',
|
||||
name: 'Wolle',
|
||||
password: 'Hello',
|
||||
passwordConfirmation: 'Hello',
|
||||
about: `Hey`,
|
||||
termsAndConditionsConfirmed: true,
|
||||
dataPrivacy: true,
|
||||
minimumAge: true,
|
||||
noCommercial: true,
|
||||
noPolitical: true,
|
||||
recieveCommunicationAsEmailsEtcConfirmed: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
@ -74,7 +111,7 @@ storiesOf('RegistrationSlider', module)
|
||||
}))
|
||||
.add('public-registration empty', () => ({
|
||||
components: { RegistrationSlider },
|
||||
store: helpers.store,
|
||||
store: createStore({ loginSuccess: true }),
|
||||
data: () => ({}),
|
||||
template: `
|
||||
<registration-slider registrationType="public-registration" />
|
||||
@ -82,23 +119,19 @@ storiesOf('RegistrationSlider', module)
|
||||
}))
|
||||
.add('public-registration with data', () => ({
|
||||
components: { RegistrationSlider },
|
||||
store: helpers.store,
|
||||
store: createStore({ loginSuccess: true }),
|
||||
data: () => ({
|
||||
overwriteSliderData: {
|
||||
collectedInputData: {
|
||||
inviteCode: null,
|
||||
email: 'wolle.huss@pjannto.com',
|
||||
emailSend: false,
|
||||
nonce: 'NTRSCZ',
|
||||
nonce: '47539',
|
||||
name: 'Wolle',
|
||||
password: 'Hello',
|
||||
passwordConfirmation: 'Hello',
|
||||
about: `Hey`,
|
||||
termsAndConditionsConfirmed: true,
|
||||
dataPrivacy: true,
|
||||
minimumAge: true,
|
||||
noCommercial: true,
|
||||
noPolitical: true,
|
||||
recieveCommunicationAsEmailsEtcConfirmed: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
@ -108,7 +141,7 @@ storiesOf('RegistrationSlider', module)
|
||||
}))
|
||||
.add('invite-mail empty', () => ({
|
||||
components: { RegistrationSlider },
|
||||
store: helpers.store,
|
||||
store: createStore({ loginSuccess: true }),
|
||||
data: () => ({
|
||||
overwriteSliderData: {
|
||||
collectedInputData: {
|
||||
@ -119,12 +152,8 @@ storiesOf('RegistrationSlider', module)
|
||||
name: null,
|
||||
password: null,
|
||||
passwordConfirmation: null,
|
||||
about: null,
|
||||
termsAndConditionsConfirmed: null,
|
||||
dataPrivacy: null,
|
||||
minimumAge: null,
|
||||
noCommercial: null,
|
||||
noPolitical: null,
|
||||
recieveCommunicationAsEmailsEtcConfirmed: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
@ -134,23 +163,19 @@ storiesOf('RegistrationSlider', module)
|
||||
}))
|
||||
.add('invite-mail with data', () => ({
|
||||
components: { RegistrationSlider },
|
||||
store: helpers.store,
|
||||
store: createStore({ loginSuccess: true }),
|
||||
data: () => ({
|
||||
overwriteSliderData: {
|
||||
collectedInputData: {
|
||||
inviteCode: null,
|
||||
email: 'wolle.huss@pjannto.com',
|
||||
emailSend: true,
|
||||
nonce: 'NTRSCZ',
|
||||
nonce: '47539',
|
||||
name: 'Wolle',
|
||||
password: 'Hello',
|
||||
passwordConfirmation: 'Hello',
|
||||
about: `Hey`,
|
||||
termsAndConditionsConfirmed: true,
|
||||
dataPrivacy: true,
|
||||
minimumAge: true,
|
||||
noCommercial: true,
|
||||
noPolitical: true,
|
||||
recieveCommunicationAsEmailsEtcConfirmed: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
@ -158,3 +183,11 @@ storiesOf('RegistrationSlider', module)
|
||||
<registration-slider registrationType="invite-mail" :overwriteSliderData="overwriteSliderData" />
|
||||
`,
|
||||
}))
|
||||
.add('no-public-registration', () => ({
|
||||
components: { RegistrationSlider },
|
||||
store: createStore({ loginSuccess: true }),
|
||||
data: () => ({}),
|
||||
template: `
|
||||
<registration-slider registrationType="no-public-registration" />
|
||||
`,
|
||||
}))
|
||||
|
||||
@ -8,37 +8,27 @@
|
||||
</template>
|
||||
|
||||
<component-slider :sliderData="sliderData">
|
||||
<template #header>
|
||||
<ds-heading size="h2">
|
||||
{{ $t('components.registration.signup.title', metadata) }}
|
||||
</ds-heading>
|
||||
<template #no-public-registration>
|
||||
<registration-slide-no-public :sliderData="sliderData" />
|
||||
</template>
|
||||
|
||||
<template v-if="['invite-code'].includes(registrationType)" #enter-invite>
|
||||
<registration-item-enter-invite :sliderData="sliderData" />
|
||||
<template #enter-invite>
|
||||
<registration-slide-invite :sliderData="sliderData" />
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-if="['invite-code', 'public-registration'].includes(registrationType)"
|
||||
#enter-email
|
||||
>
|
||||
<!-- Wolle !!! may create same source with 'webapp/pages/registration/signup.vue' -->
|
||||
<!-- <signup v-if="publicRegistration" :invitation="false" @submit="handleSubmitted"> -->
|
||||
<registration-item-enter-email :sliderData="sliderData" :invitation="false" />
|
||||
<template #enter-email>
|
||||
<registration-slide-email :sliderData="sliderData" :invitation="false" />
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-if="['invite-code', 'public-registration', 'invite-mail'].includes(registrationType)"
|
||||
#enter-nonce
|
||||
>
|
||||
<registration-item-enter-nonce :sliderData="sliderData" />
|
||||
<template #enter-nonce>
|
||||
<registration-slide-nonce :sliderData="sliderData" />
|
||||
</template>
|
||||
|
||||
<template #create-user-account>
|
||||
<registration-item-create-user-account :sliderData="sliderData" />
|
||||
<registration-slide-create :sliderData="sliderData" />
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<template v-if="registrationType !== 'no-public-registration'" #footer>
|
||||
<ds-space margin-bottom="xxx-small" margin-top="small" centered>
|
||||
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
|
||||
</ds-space>
|
||||
@ -57,93 +47,113 @@ import links from '~/constants/links.js'
|
||||
import metadata from '~/constants/metadata.js'
|
||||
import ComponentSlider from '~/components/ComponentSlider/ComponentSlider'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||
import RegistrationItemCreateUserAccount from './RegistrationItemCreateUserAccount'
|
||||
import RegistrationItemEnterEmail from '~/components/Registration/RegistrationItemEnterEmail'
|
||||
import RegistrationItemEnterInvite from './RegistrationItemEnterInvite'
|
||||
import RegistrationItemEnterNonce from './RegistrationItemEnterNonce'
|
||||
import RegistrationSlideCreate from './RegistrationSlideCreate'
|
||||
import RegistrationSlideEmail from './RegistrationSlideEmail'
|
||||
import RegistrationSlideInvite from './RegistrationSlideInvite'
|
||||
import RegistrationSlideNonce from './RegistrationSlideNonce'
|
||||
import RegistrationSlideNoPublic from './RegistrationSlideNoPublic'
|
||||
|
||||
export default {
|
||||
name: 'RegistrationSlider',
|
||||
components: {
|
||||
ComponentSlider,
|
||||
LocaleSwitch,
|
||||
RegistrationItemCreateUserAccount,
|
||||
RegistrationItemEnterEmail,
|
||||
RegistrationItemEnterInvite,
|
||||
RegistrationItemEnterNonce,
|
||||
RegistrationSlideCreate,
|
||||
RegistrationSlideEmail,
|
||||
RegistrationSlideInvite,
|
||||
RegistrationSlideNonce,
|
||||
RegistrationSlideNoPublic,
|
||||
},
|
||||
props: {
|
||||
registrationType: { type: String, required: true },
|
||||
overwriteSliderData: { type: Object, default: () => {} },
|
||||
},
|
||||
data() {
|
||||
const slidersPortfolio = [
|
||||
{
|
||||
const slidersPortfolio = {
|
||||
noPublicRegistration: {
|
||||
name: 'no-public-registration',
|
||||
titleIdent: 'components.registration.no-public-registrstion.title',
|
||||
validated: false,
|
||||
data: { request: null, response: null },
|
||||
button: {
|
||||
titleIdent: 'site.back-to-login',
|
||||
icon: null,
|
||||
callback: this.buttonCallback,
|
||||
sliderCallback: null, // optional set by slot
|
||||
},
|
||||
},
|
||||
enterInvite: {
|
||||
name: 'enter-invite',
|
||||
// title: this.$t('components.registration.create-user-account.title'),
|
||||
title: 'Invitation', // Wolle
|
||||
titleIdent: { id: 'components.registration.signup.title', data: metadata },
|
||||
validated: false,
|
||||
data: { request: null, response: { isValidInviteCode: false } },
|
||||
button: {
|
||||
title: 'Next', // Wolle
|
||||
titleIdent: 'components.registration.invite-code.buttonTitle',
|
||||
icon: 'arrow-right',
|
||||
callback: this.buttonCallback,
|
||||
sliderCallback: null, // optional set by slot
|
||||
},
|
||||
},
|
||||
{
|
||||
enterEmail: {
|
||||
name: 'enter-email',
|
||||
title: 'E-Mail', // Wolle
|
||||
titleIdent: 'components.registration.email.title',
|
||||
validated: false,
|
||||
data: { request: null, response: null },
|
||||
button: {
|
||||
title: '', // set by slider component
|
||||
icon: '', // set by slider component
|
||||
titleIdent: 'components.registration.email.buttonTitle.send', // changed by slider component
|
||||
icon: 'envelope', // changed by slider component
|
||||
callback: this.buttonCallback,
|
||||
sliderCallback: null, // optional set by slot
|
||||
},
|
||||
},
|
||||
{
|
||||
enterNonce: {
|
||||
name: 'enter-nonce',
|
||||
title: 'E-Mail Confirmation', // Wolle
|
||||
titleIdent: 'components.registration.email-nonce.title',
|
||||
validated: false,
|
||||
data: { request: null, response: null },
|
||||
data: { request: null, response: { VerifyNonce: false } },
|
||||
button: {
|
||||
title: 'Confirm', // Wolle
|
||||
titleIdent: 'components.registration.email-nonce.buttonTitle',
|
||||
icon: 'arrow-right',
|
||||
callback: this.buttonCallback,
|
||||
sliderCallback: null, // optional set by slot
|
||||
},
|
||||
},
|
||||
{
|
||||
createUserAccount: {
|
||||
name: 'create-user-account',
|
||||
title: this.$t('components.registration.create-user-account.title'),
|
||||
titleIdent: 'components.registration.create-user-account.title',
|
||||
validated: false,
|
||||
data: { request: null, response: null },
|
||||
button: {
|
||||
// title: this.$t('actions.save'), // Wolle
|
||||
title: 'Create', // Wolle
|
||||
titleIdent: 'components.registration.create-user-account.buttonTitle',
|
||||
icon: 'check',
|
||||
loading: false,
|
||||
callback: this.buttonCallback,
|
||||
sliderCallback: null, // optional set by slot
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
let sliders = []
|
||||
switch (this.registrationType) {
|
||||
case 'no-public-registration':
|
||||
sliders = [slidersPortfolio.noPublicRegistration]
|
||||
break
|
||||
case 'invite-code':
|
||||
sliders = [
|
||||
slidersPortfolio[0],
|
||||
slidersPortfolio[1],
|
||||
slidersPortfolio[2],
|
||||
slidersPortfolio[3],
|
||||
slidersPortfolio.enterInvite,
|
||||
slidersPortfolio.enterEmail,
|
||||
slidersPortfolio.enterNonce,
|
||||
slidersPortfolio.createUserAccount,
|
||||
]
|
||||
break
|
||||
case 'public-registration':
|
||||
sliders = [slidersPortfolio[1], slidersPortfolio[2], slidersPortfolio[3]]
|
||||
sliders = [
|
||||
slidersPortfolio.enterEmail,
|
||||
slidersPortfolio.enterNonce,
|
||||
slidersPortfolio.createUserAccount,
|
||||
]
|
||||
break
|
||||
case 'invite-mail':
|
||||
sliders = [slidersPortfolio[2], slidersPortfolio[3]]
|
||||
sliders = [slidersPortfolio.enterNonce, slidersPortfolio.createUserAccount]
|
||||
break
|
||||
}
|
||||
|
||||
@ -159,12 +169,8 @@ export default {
|
||||
name: null,
|
||||
password: null,
|
||||
passwordConfirmation: null,
|
||||
about: null,
|
||||
termsAndConditionsConfirmed: null,
|
||||
dataPrivacy: null,
|
||||
minimumAge: null,
|
||||
noCommercial: null,
|
||||
noPolitical: null,
|
||||
recieveCommunicationAsEmailsEtcConfirmed: null,
|
||||
},
|
||||
sliderIndex: 0,
|
||||
sliders: sliders,
|
||||
@ -180,11 +186,15 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setSliderValuesCallback(isValid, { collectedInputData, sliderData, sliderSettings }) {
|
||||
setSliderValuesCallback(
|
||||
isValid = null,
|
||||
{ collectedInputData, sliderData, sliderSettings } = {},
|
||||
) {
|
||||
// all changes of 'this.sliders' has to be filled in from the top to be spread to the component slider and all slider components in the slot
|
||||
|
||||
this.sliderData.sliders[this.sliderIndex].validated = isValid
|
||||
|
||||
if (isValid !== null) {
|
||||
this.sliderData.sliders[this.sliderIndex].validated = isValid
|
||||
}
|
||||
if (collectedInputData) {
|
||||
this.sliderData.collectedInputData = {
|
||||
...this.sliderData.collectedInputData,
|
||||
@ -204,14 +214,17 @@ export default {
|
||||
}
|
||||
}
|
||||
if (sliderSettings) {
|
||||
const { buttonTitle, buttonIcon, buttonSliderCallback } = sliderSettings
|
||||
if (buttonTitle) {
|
||||
this.sliderData.sliders[this.sliderIndex].button.title = buttonTitle
|
||||
const { buttonTitleIdent, buttonIcon, buttonLoading, buttonSliderCallback } = sliderSettings
|
||||
if (buttonTitleIdent !== undefined) {
|
||||
this.sliderData.sliders[this.sliderIndex].button.titleIdent = buttonTitleIdent
|
||||
}
|
||||
if (buttonIcon) {
|
||||
if (buttonIcon !== undefined) {
|
||||
this.sliderData.sliders[this.sliderIndex].button.icon = buttonIcon
|
||||
}
|
||||
if (buttonSliderCallback) {
|
||||
if (buttonLoading !== undefined) {
|
||||
this.sliderData.sliders[this.sliderIndex].button.loading = buttonLoading
|
||||
}
|
||||
if (buttonSliderCallback !== undefined) {
|
||||
this.sliderData.sliders[this.sliderIndex].button.sliderCallback = buttonSliderCallback
|
||||
}
|
||||
}
|
||||
@ -221,6 +234,10 @@ export default {
|
||||
|
||||
if (selectedIndex <= this.sliderIndex + 1 && selectedIndex < this.sliderData.sliders.length) {
|
||||
this.sliderData.sliderIndex = selectedIndex
|
||||
|
||||
if (this.sliderData.sliders[this.sliderIndex].button.loading !== undefined) {
|
||||
this.sliderData.sliders[this.sliderIndex].button.loading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
buttonCallback(success) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import Signup, { SignupMutation, SignupByInvitationMutation } from './Signup'
|
||||
import Signup, { SignupMutation } from './Signup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -58,7 +58,8 @@ describe('Signup', () => {
|
||||
|
||||
it('delivers email to backend', () => {
|
||||
const expected = expect.objectContaining({
|
||||
variables: { email: 'mAIL@exAMPLE.org', token: null },
|
||||
mutation: SignupMutation,
|
||||
variables: { email: 'mAIL@exAMPLE.org', inviteCode: null },
|
||||
})
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
@ -84,68 +85,5 @@ describe('Signup', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with invitation code', () => {
|
||||
let action
|
||||
beforeEach(() => {
|
||||
propsData.token = '666777'
|
||||
action = async () => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('input#email').setValue('mail@example.org')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await wrapper.html()
|
||||
}
|
||||
})
|
||||
|
||||
describe('submit', () => {
|
||||
it('calls SignupByInvitation graphql mutation', async () => {
|
||||
await action()
|
||||
const expected = expect.objectContaining({ mutation: SignupByInvitationMutation })
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
it('delivers invitation token to backend', async () => {
|
||||
await action()
|
||||
const expected = expect.objectContaining({
|
||||
variables: { email: 'mail@example.org', token: '666777' },
|
||||
})
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
describe('in case a user account with the email already exists', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$apollo.mutate = jest
|
||||
.fn()
|
||||
.mockRejectedValue(
|
||||
new Error('UserInputError: A user account with this email already exists.'),
|
||||
)
|
||||
})
|
||||
|
||||
it('explains the error', async () => {
|
||||
await action()
|
||||
expect(mocks.$t).toHaveBeenCalledWith(
|
||||
'components.registration.signup.form.errors.email-exists',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('in case the invitation code was incorrect', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$apollo.mutate = jest
|
||||
.fn()
|
||||
.mockRejectedValue(
|
||||
new Error('UserInputError: Invitation code already used or does not exist.'),
|
||||
)
|
||||
})
|
||||
|
||||
it('explains the error', async () => {
|
||||
await action()
|
||||
expect(mocks.$t).toHaveBeenCalledWith(
|
||||
'components.registration.signup.form.errors.invalid-invitation-token',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,9 +14,6 @@
|
||||
: $t('components.registration.signup.title', metadata)
|
||||
}}
|
||||
</h1>
|
||||
<ds-space v-if="token" margin-botton="large">
|
||||
<ds-text v-html="$t('registration.signup.form.invitation-code', { code: token })" />
|
||||
</ds-space>
|
||||
<ds-space margin-botton="large">
|
||||
<ds-text>
|
||||
{{
|
||||
@ -70,17 +67,11 @@
|
||||
import gql from 'graphql-tag'
|
||||
import metadata from '~/constants/metadata'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
|
||||
|
||||
export const SignupMutation = gql`
|
||||
mutation($email: String!) {
|
||||
Signup(email: $email) {
|
||||
email
|
||||
}
|
||||
}
|
||||
`
|
||||
export const SignupByInvitationMutation = gql`
|
||||
mutation($email: String!, $token: String!) {
|
||||
SignupByInvitation(email: $email, token: $token) {
|
||||
mutation($email: String!, $inviteCode: String) {
|
||||
Signup(email: $email, inviteCode: $inviteCode) {
|
||||
email
|
||||
}
|
||||
}
|
||||
@ -91,7 +82,6 @@ export default {
|
||||
SweetalertIcon,
|
||||
},
|
||||
props: {
|
||||
token: { type: String, default: null },
|
||||
invitation: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
@ -126,32 +116,28 @@ export default {
|
||||
this.disabled = false
|
||||
},
|
||||
async handleSubmit() {
|
||||
const mutation = this.token ? SignupByInvitationMutation : SignupMutation
|
||||
const { token } = this
|
||||
const { email } = this.formData
|
||||
|
||||
try {
|
||||
const response = await this.$apollo.mutate({ mutation, variables: { email, token } })
|
||||
const response = await this.$apollo.mutate({
|
||||
mutation: SignupMutation,
|
||||
variables: { email, inviteCode: null },
|
||||
})
|
||||
this.data = response.data
|
||||
setTimeout(() => {
|
||||
this.$emit('submit', { email: this.data.Signup.email })
|
||||
}, 3000)
|
||||
} catch (err) {
|
||||
const { message } = err
|
||||
const mapping = {
|
||||
'A user account with this email already exists': 'email-exists',
|
||||
'Invitation code already used or does not exist': 'invalid-invitation-token',
|
||||
}
|
||||
for (const [pattern, key] of Object.entries(mapping)) {
|
||||
if (message.includes(pattern))
|
||||
this.error = {
|
||||
key,
|
||||
message: this.$t(`components.registration.signup.form.errors.${key}`),
|
||||
}
|
||||
}
|
||||
if (!this.error) {
|
||||
this.$toast.error(message)
|
||||
}
|
||||
this.$toast.error(
|
||||
translateErrorMessage(
|
||||
err.message,
|
||||
{
|
||||
'A user account with this email already exists':
|
||||
'components.registration.signup.form.errors.email-exists',
|
||||
},
|
||||
this.$t,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
12
webapp/components/utils/TranslateErrorMessage.js
Normal file
12
webapp/components/utils/TranslateErrorMessage.js
Normal file
@ -0,0 +1,12 @@
|
||||
export default (message, mapping, translate) => {
|
||||
let translatedMessage = null
|
||||
for (const [pattern, ident] of Object.entries(mapping)) {
|
||||
if (message.includes(pattern)) {
|
||||
translatedMessage = translate(ident)
|
||||
}
|
||||
}
|
||||
if (!translatedMessage) {
|
||||
translatedMessage = message
|
||||
}
|
||||
return translatedMessage
|
||||
}
|
||||
@ -4,6 +4,7 @@ export const SignupVerificationMutation = gql`
|
||||
$nonce: String!
|
||||
$name: String!
|
||||
$email: String!
|
||||
$inviteCode: String
|
||||
$password: String!
|
||||
$about: String
|
||||
$termsAndConditionsAgreedVersion: String!
|
||||
@ -12,6 +13,7 @@ export const SignupVerificationMutation = gql`
|
||||
SignupVerification(
|
||||
nonce: $nonce
|
||||
email: $email
|
||||
inviteCode: $inviteCode
|
||||
name: $name
|
||||
password: $password
|
||||
about: $about
|
||||
|
||||
@ -120,26 +120,6 @@
|
||||
"versus": "Versus"
|
||||
},
|
||||
"components": {
|
||||
"enter-invite": {
|
||||
"form": {
|
||||
"description": "Gib den Einladungs-Code ein, den du bekommen hast.",
|
||||
"invite-code": "Einladungs-Code eingeben",
|
||||
"next": "Weiter",
|
||||
"validations": {
|
||||
"length": "muss genau 6 Buchstaben lang sein"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enter-nonce": {
|
||||
"form": {
|
||||
"description": "Öffne Dein E-Mail Postfach und gib den Code ein, den wir geschickt haben.",
|
||||
"next": "Weiter",
|
||||
"nonce": "Code eingeben",
|
||||
"validations": {
|
||||
"length": "muss genau 6 Buchstaben lang sein"
|
||||
}
|
||||
}
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "Passwort Änderung fehlgeschlagen. Möglicherweise falscher Sicherheitscode?",
|
||||
@ -157,33 +137,68 @@
|
||||
},
|
||||
"registration": {
|
||||
"create-user-account": {
|
||||
"buttonTitle": "Erstellen",
|
||||
"error": "Es konnte kein Benutzerkonto erstellt werden!",
|
||||
"help": "Vielleicht war der Bestätigungscode falsch oder abgelaufen? Wenn das Problem weiterhin besteht, schicke uns gerne eine E-Mail an:",
|
||||
"recieveCommunicationAsEmailsEtcConfirmed": "Ich stimme auch dem Erhalt von E-Mails und anderen Formen der Kommunikation (z.B. Push-Benachrichtigungen) zu.",
|
||||
"success": "Dein Benutzerkonto wurde erstellt!",
|
||||
"termsAndCondsEtcConfirmed": "Ich habe folgendes gelesen, verstanden und stimme zu:",
|
||||
"title": "Benutzerkonto anlegen"
|
||||
},
|
||||
"email": {
|
||||
"buttonTitle": {
|
||||
"resend": "Erneut senden",
|
||||
"send": "Sende E-Mail",
|
||||
"skipResend": "Nicht senden"
|
||||
},
|
||||
"form": {
|
||||
"sendEmailAgain": "E-Mail erneut senden",
|
||||
"success": "Verifikations-E-Mail gesendet an <b>{email}</b>!"
|
||||
}
|
||||
},
|
||||
"title": "E-Mail"
|
||||
},
|
||||
"email-display": {
|
||||
"warningFormat": "⚠️ E-Mail hat ein ungültiges Format!",
|
||||
"warningUndef": "⚠️ Keine E-Mail definiert!",
|
||||
"yourEmail": "Deine E-Mail-Adresse:"
|
||||
},
|
||||
"email-nonce": {
|
||||
"buttonTitle": "Bestätigen",
|
||||
"form": {
|
||||
"description": "Öffne Dein E-Mail Postfach und gib den Code ein, den wir geschickt haben.",
|
||||
"next": "Weiter",
|
||||
"nonce": "E-Mail-Code: 32143",
|
||||
"validations": {
|
||||
"error": "Ungültiger Bestätigungs-Code <b>{nonce}</b> für E-Mail <b>{email}</b>!",
|
||||
"length": "muss genau 5 Buchstaben lang sein",
|
||||
"success": "Gültiger Bestätigungs-Code <b>{nonce}</b> für E-Mail <b>{email}</b>!"
|
||||
}
|
||||
},
|
||||
"title": "E-Mail Bestätigung"
|
||||
},
|
||||
"invite-code": {
|
||||
"buttonTitle": "Weiter",
|
||||
"form": {
|
||||
"success": "Gültiger Einladungs-Code <b>{inviteCode}</b>!"
|
||||
"description": "Gib den Einladungs-Code ein, den du bekommen hast.",
|
||||
"invite-code": "Einladungs-Code: ACJERB",
|
||||
"next": "Weiter",
|
||||
"validations": {
|
||||
"error": "Ungültiger Einladungs-Code <b>{inviteCode}</b>!",
|
||||
"length": "muss genau 6 Buchstaben lang sein",
|
||||
"success": "Gültiger Einladungs-Code <b>{inviteCode}</b>!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"no-public-registrstion": {
|
||||
"title": "Keine öffentliche Registrierung möglich"
|
||||
},
|
||||
"signup": {
|
||||
"form": {
|
||||
"data-privacy": "Ich habe die Datenschutzerklärung gelesen und verstanden.",
|
||||
"description": "Um loszulegen, kannst Du Dich hier kostenfrei registrieren:",
|
||||
"errors": {
|
||||
"email-exists": "Es gibt schon ein Benutzerkonto mit dieser E-Mail-Adresse!",
|
||||
"invalid-invitation-token": "Es sieht so aus, als ob der Einladungscode schon eingelöst wurde. Jeder Code kann nur einmalig benutzt werden."
|
||||
"email-exists": "Es gibt schon ein Benutzerkonto mit dieser E-Mail-Adresse!"
|
||||
},
|
||||
"invitation-code": "Dein Einladungscode lautet: <b>{code}</b>",
|
||||
"minimum-age": "Ich bin 18 Jahre oder älter.",
|
||||
"no-commercial": "Ich habe keine kommerziellen Absichten und ich repräsentiere kein kommerzielles Unternehmen oder Organisation.",
|
||||
"no-political": "Ich bin nicht im Auftrag einer Partei oder politischen Organisation im Netzwerk.",
|
||||
"submit": "Konto erstellen",
|
||||
"success": "Eine E-Mail mit einem Link zum Abschließen Deiner Registrierung wurde an <b>{email}</b> geschickt",
|
||||
"terms-and-condition": "Ich stimme den <a href=\"/terms-and-conditions\" target=\"_blank\"><ds-text bold color=\"primary\">Nutzungsbedingungen</ds-text></a> zu."
|
||||
@ -786,9 +801,7 @@
|
||||
}
|
||||
},
|
||||
"termsAndConditions": {
|
||||
"agree": "Ich stimme zu!",
|
||||
"newTermsAndConditions": "Neue Nutzungsbedingungen",
|
||||
"termsAndConditionsConfirmed": "Ich habe die Nutzungsbedingungen durchgelesen und stimme ihnen zu.",
|
||||
"termsAndConditionsNewConfirm": "Ich habe die neuen Nutzungsbedingungen durchgelesen und stimme zu.",
|
||||
"termsAndConditionsNewConfirmText": "Bitte lies Dir die neuen Nutzungsbedingungen jetzt durch!"
|
||||
},
|
||||
|
||||
@ -120,26 +120,6 @@
|
||||
"versus": "Versus"
|
||||
},
|
||||
"components": {
|
||||
"enter-invite": {
|
||||
"form": {
|
||||
"description": "Enter the invitation code you received.",
|
||||
"invite-code": "Enter your invite code",
|
||||
"next": "Continue",
|
||||
"validations": {
|
||||
"length": "must be 6 characters long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enter-nonce": {
|
||||
"form": {
|
||||
"description": "Open your inbox and enter the code that we've sent to you.",
|
||||
"next": "Continue",
|
||||
"nonce": "Enter your code",
|
||||
"validations": {
|
||||
"length": "must be 6 characters long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "Changing your password failed. Maybe the security code was not correct?",
|
||||
@ -157,33 +137,68 @@
|
||||
},
|
||||
"registration": {
|
||||
"create-user-account": {
|
||||
"buttonTitle": "Create",
|
||||
"error": "No user account could be created!",
|
||||
"help": " Maybe the confirmation was invalid? In case of problems, feel free to ask for help by sending us a mail to:",
|
||||
"recieveCommunicationAsEmailsEtcConfirmed": "I also agree to receive e-mails and other forms of communication (e.g. push notifications).",
|
||||
"success": "Your account has been created!",
|
||||
"termsAndCondsEtcConfirmed": "I have read, understand and agree to the following:",
|
||||
"title": "Create user account"
|
||||
},
|
||||
"email": {
|
||||
"buttonTitle": {
|
||||
"resend": "Resend e-mail",
|
||||
"send": "Send e-mail",
|
||||
"skipResend": "Skip resend"
|
||||
},
|
||||
"form": {
|
||||
"sendEmailAgain": "Send e-mail again",
|
||||
"success": "Verification e-mail send to <b>{email}</b>!"
|
||||
}
|
||||
},
|
||||
"title": "E-Mail"
|
||||
},
|
||||
"email-display": {
|
||||
"warningFormat": "⚠️ E-mail has wrong format!",
|
||||
"warningUndef": "⚠️ No e-mail defined!",
|
||||
"yourEmail": "Your e-mail address:"
|
||||
},
|
||||
"email-nonce": {
|
||||
"buttonTitle": "Confirm",
|
||||
"form": {
|
||||
"description": "Open your inbox and enter the code that we've sent to you.",
|
||||
"next": "Continue",
|
||||
"nonce": "E-mail code: 32143",
|
||||
"validations": {
|
||||
"error": "Invalid verification code <b>{nonce}</b> for e-mail <b>{email}</b>!",
|
||||
"length": "must be 5 characters long",
|
||||
"success": "Valid verification code <b>{nonce}</b> for e-mail <b>{email}</b>!"
|
||||
}
|
||||
},
|
||||
"title": "E-Mail Confirmation"
|
||||
},
|
||||
"invite-code": {
|
||||
"buttonTitle": "Next",
|
||||
"form": {
|
||||
"success": "Valid invite code <b>{inviteCode}</b>!"
|
||||
"description": "Enter the invitation code you received.",
|
||||
"invite-code": "Invite code: ACJERB",
|
||||
"next": "Continue",
|
||||
"validations": {
|
||||
"error": "Invalid invite code <b>{inviteCode}</b>!",
|
||||
"length": "must be 6 characters long",
|
||||
"success": "Valid invite code <b>{inviteCode}</b>!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"no-public-registrstion": {
|
||||
"title": "No Public Registration"
|
||||
},
|
||||
"signup": {
|
||||
"form": {
|
||||
"data-privacy": "I have read and understood the privacy statement.",
|
||||
"description": "To get started, you can register here for free:",
|
||||
"errors": {
|
||||
"email-exists": "There is already a user account with this e-mail address!",
|
||||
"invalid-invitation-token": "It looks like as if the invitation has been used already. Invitation links can only be used once."
|
||||
"email-exists": "There is already a user account with this e-mail address!"
|
||||
},
|
||||
"invitation-code": "Your invitation code is: <b>{code}</b>",
|
||||
"minimum-age": "I'm 18 years or older.",
|
||||
"no-commercial": "I have no commercial interests and I am not representing a company or any other commercial organisation on the network.",
|
||||
"no-political": "I am not on behalf of a party or political organization in the network.",
|
||||
"submit": "Create an account",
|
||||
"success": "A mail with a link to complete your registration has been sent to <b>{email}</b>",
|
||||
"terms-and-condition": "I confirm to the <a href=\"/terms-and-conditions\" target=\"_blank\"><ds-text bold color=\"primary\">Terms and conditions</ds-text></a>."
|
||||
@ -786,9 +801,7 @@
|
||||
}
|
||||
},
|
||||
"termsAndConditions": {
|
||||
"agree": "I agree!",
|
||||
"newTermsAndConditions": "New Terms and Conditions",
|
||||
"termsAndConditionsConfirmed": "I have read and confirmed the terms and conditions.",
|
||||
"termsAndConditionsNewConfirm": "I have read and agree to the new terms of conditions.",
|
||||
"termsAndConditionsNewConfirmText": "Please read the new terms of use now!"
|
||||
},
|
||||
|
||||
@ -116,16 +116,6 @@
|
||||
"versus": "Versus"
|
||||
},
|
||||
"components": {
|
||||
"enter-nonce": {
|
||||
"form": {
|
||||
"description": "Abra su buzón de correo e introduzca el código que le enviamos.",
|
||||
"next": "Continuar",
|
||||
"nonce": "Introduzca el código",
|
||||
"validations": {
|
||||
"length": "debe tener exactamente 6 letras"
|
||||
}
|
||||
}
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "Error al cambiar la contraseña. ¿Posiblemente un código de seguridad incorrecto?",
|
||||
@ -148,18 +138,23 @@
|
||||
"success": "¡Su cuenta de usuario ha sido creada!",
|
||||
"title": "Crear una cuenta de usuario"
|
||||
},
|
||||
"email-nonce": {
|
||||
"form": {
|
||||
"description": "Abra su buzón de correo e introduzca el código que le enviamos.",
|
||||
"next": "Continuar",
|
||||
"nonce": "Introduzca el código",
|
||||
"validations": {
|
||||
"length": "debe tener exactamente 5 letras"
|
||||
}
|
||||
}
|
||||
},
|
||||
"signup": {
|
||||
"form": {
|
||||
"data-privacy": "He leido y entendido la declaración de protección de datos.",
|
||||
"description": "Para empezar, introduzca su dirección de correo electrónico:",
|
||||
"errors": {
|
||||
"email-exists": "¡Ya hay una cuenta de usuario con esta dirección de correo electrónico!",
|
||||
"invalid-invitation-token": "Parece que el código de invitación ya ha sido canjeado. Cada código sólo se puede utilizar una vez."
|
||||
"email-exists": "¡Ya hay una cuenta de usuario con esta dirección de correo electrónico!"
|
||||
},
|
||||
"invitation-code": "Su código de invitación es: <b>{code}</b>",
|
||||
"minimum-age": "Tengo 18 años o más.",
|
||||
"no-commercial": "No tengo intensiones comerciales y no represento una empresa u organización comercial.",
|
||||
"no-political": "No estoy en la red en nombre de un partido o una organización política.",
|
||||
"submit": "Crear una cuenta",
|
||||
"success": "Se ha enviado un correo electrónico con un enlace de confirmación para el registro a <b>{email}</b>.",
|
||||
"terms-and-condition": "Estoy de acuerdo con los términos de uso."
|
||||
@ -729,9 +724,7 @@
|
||||
}
|
||||
},
|
||||
"termsAndConditions": {
|
||||
"agree": "¡Estoy de acuerdo!",
|
||||
"newTermsAndConditions": "Nuevos términos de uso",
|
||||
"termsAndConditionsConfirmed": "He leído y acepto los términos de uso.",
|
||||
"termsAndConditionsNewConfirm": "He leído y acepto los nuevos términos de uso.",
|
||||
"termsAndConditionsNewConfirmText": "¡Por favor, lea los nuevos términos de uso ahora!"
|
||||
},
|
||||
|
||||
@ -116,16 +116,6 @@
|
||||
"versus": "Versus"
|
||||
},
|
||||
"components": {
|
||||
"enter-nonce": {
|
||||
"form": {
|
||||
"description": "Ouvrez votre boîte de réception et entrez le code que nous vous avons envoyé.",
|
||||
"next": "Continuer",
|
||||
"nonce": "Entrez votre code",
|
||||
"validations": {
|
||||
"length": "doit comporter 6 caractères"
|
||||
}
|
||||
}
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "La modification de votre mot de passe a échoué. Peut-être que le code de sécurité n'était pas correct ?",
|
||||
@ -148,18 +138,23 @@
|
||||
"success": "Votre compte a été créé!",
|
||||
"title": "Créer un compte utilisateur"
|
||||
},
|
||||
"email-nonce": {
|
||||
"form": {
|
||||
"description": "Ouvrez votre boîte de réception et entrez le code que nous vous avons envoyé.",
|
||||
"next": "Continuer",
|
||||
"nonce": "Entrez votre code",
|
||||
"validations": {
|
||||
"length": "doit comporter 5 caractères"
|
||||
}
|
||||
}
|
||||
},
|
||||
"signup": {
|
||||
"form": {
|
||||
"data-privacy": "J'ai lu et compris la Déclaration de confidentialité.",
|
||||
"description": "Pour commencer, entrez votre adresse mail :",
|
||||
"errors": {
|
||||
"email-exists": "Il existe déjà un compte utilisateur avec cette adresse mail!",
|
||||
"invalid-invitation-token": "On dirait que l'invitation a déjà été utilisée. Les liens d'invitation ne peuvent être utilisés qu'une seule fois."
|
||||
"email-exists": "Il existe déjà un compte utilisateur avec cette adresse mail!"
|
||||
},
|
||||
"invitation-code": "Votre code d'invitation est: <b> {code} </b>",
|
||||
"minimum-age": "J'ai 18 ans ou plus.",
|
||||
"no-commercial": "Je n'ai aucun intérêt commercial et je ne représente pas d'entreprise ou toute autre organisation commerciale sur le réseau.",
|
||||
"no-political": "Je ne parle pas au nom d'un parti ou d'une organisation politique sur le réseau.",
|
||||
"submit": "Créer un compte",
|
||||
"success": "Un mail avec un lien pour compléter votre inscription a été envoyé à <b>{email}</b>",
|
||||
"terms-and-condition": "Je confirme les Conditions générales."
|
||||
@ -697,9 +692,7 @@
|
||||
}
|
||||
},
|
||||
"termsAndConditions": {
|
||||
"agree": "J'accepte!",
|
||||
"newTermsAndConditions": "Nouvelles conditions générales",
|
||||
"termsAndConditionsConfirmed": "J'ai lu et accepte les conditions générales.",
|
||||
"termsAndConditionsNewConfirm": "J'ai lu et accepté les nouvelles conditions générales.",
|
||||
"termsAndConditionsNewConfirmText": "Veuillez lire les nouvelles conditions d'utilisation dès maintenant !"
|
||||
},
|
||||
|
||||
@ -123,16 +123,6 @@
|
||||
"versus": "Verso"
|
||||
},
|
||||
"components": {
|
||||
"enter-nonce": {
|
||||
"form": {
|
||||
"description": null,
|
||||
"next": null,
|
||||
"nonce": null,
|
||||
"validations": {
|
||||
"length": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "Modifica della password non riuscita. Forse il codice di sicurezza non era corretto?",
|
||||
@ -155,16 +145,23 @@
|
||||
"success": null,
|
||||
"title": null
|
||||
},
|
||||
"email-nonce": {
|
||||
"form": {
|
||||
"description": null,
|
||||
"next": null,
|
||||
"nonce": null,
|
||||
"validations": {
|
||||
"length": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"signup": {
|
||||
"form": {
|
||||
"data-privacy": null,
|
||||
"description": null,
|
||||
"errors": {
|
||||
"email-exists": null,
|
||||
"invalid-invitation-token": "Sembra che l'invito sia già stato utilizzato. I link di invito possono essere utilizzati una sola volta."
|
||||
"email-exists": null
|
||||
},
|
||||
"invitation-code": null,
|
||||
"minimum-age": null,
|
||||
"submit": null,
|
||||
"success": null,
|
||||
"terms-and-condition": null
|
||||
@ -645,9 +642,7 @@
|
||||
}
|
||||
},
|
||||
"termsAndConditions": {
|
||||
"agree": "Sono d'accordo!",
|
||||
"newTermsAndConditions": "Nuovi Termini e Condizioni",
|
||||
"termsAndConditionsConfirmed": "Ho letto e confermato i Termini e condizioni.",
|
||||
"termsAndConditionsNewConfirm": "Ho letto e accetto le nuove condizioni generali di contratto.",
|
||||
"termsAndConditionsNewConfirmText": "Si prega di leggere le nuove condizioni d'uso ora!"
|
||||
},
|
||||
|
||||
@ -83,16 +83,6 @@
|
||||
"versus": "werset"
|
||||
},
|
||||
"components": {
|
||||
"enter-nonce": {
|
||||
"form": {
|
||||
"description": "Otwórz swoją skrzynkę odbiorczą i wpisz kod, który do Ciebie wysłaliśmy.",
|
||||
"next": "Kontynuuj",
|
||||
"nonce": "Wprowadź swój kod",
|
||||
"validations": {
|
||||
"length": "musi mieć długość 6 znaków."
|
||||
}
|
||||
}
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "Zmiana hasła nie powiodła się. Może kod bezpieczeństwa nie był poprawny?",
|
||||
@ -107,6 +97,18 @@
|
||||
},
|
||||
"title": "Zresetuj hasło"
|
||||
}
|
||||
},
|
||||
"registration": {
|
||||
"email-nonce": {
|
||||
"form": {
|
||||
"description": "Otwórz swoją skrzynkę odbiorczą i wpisz kod, który do Ciebie wysłaliśmy.",
|
||||
"next": "Kontynuuj",
|
||||
"nonce": "Wprowadź swój kod",
|
||||
"validations": {
|
||||
"length": "musi mieć długość 5 znaków."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"contribution": {
|
||||
|
||||
@ -163,16 +163,6 @@
|
||||
"versus": "Contra"
|
||||
},
|
||||
"components": {
|
||||
"enter-nonce": {
|
||||
"form": {
|
||||
"description": "Abra a sua caixa de entrada e digite o código que lhe enviamos.",
|
||||
"next": "Continue",
|
||||
"nonce": "Digite seu código",
|
||||
"validations": {
|
||||
"length": "deve ter 6 caracteres"
|
||||
}
|
||||
}
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "A alteração da sua senha falhou. Talvez o código de segurança não estava correto?",
|
||||
@ -195,16 +185,23 @@
|
||||
"success": "A sua conta foi criada!",
|
||||
"title": "Criar uma conta de usuário"
|
||||
},
|
||||
"email-nonce": {
|
||||
"form": {
|
||||
"description": "Abra a sua caixa de entrada e digite o código que lhe enviamos.",
|
||||
"next": "Continue",
|
||||
"nonce": "Digite seu código",
|
||||
"validations": {
|
||||
"length": "deve ter 5 caracteres"
|
||||
}
|
||||
}
|
||||
},
|
||||
"signup": {
|
||||
"form": {
|
||||
"data-privacy": "Eu li e entendi o Política de Privacidade.",
|
||||
"description": "Para começar, digite seu endereço de e-mail:",
|
||||
"errors": {
|
||||
"email-exists": "Já existe uma conta de usuário com este endereço de e-mail!",
|
||||
"invalid-invitation-token": "Parece que o convite já foi usado. Os links para convites só podem ser usados uma vez."
|
||||
"email-exists": "Já existe uma conta de usuário com este endereço de e-mail!"
|
||||
},
|
||||
"invitation-code": "O seu código de convite é: <b>{code}</b>",
|
||||
"minimum-age": "Tenho 18 anos ou mais.",
|
||||
"submit": "Criar uma conta",
|
||||
"success": "Um e-mail com um link para completar o seu registo foi enviado para <b>{email}</b>",
|
||||
"terms-and-condition": "Eu concordo com os Termos e condições."
|
||||
@ -680,9 +677,7 @@
|
||||
}
|
||||
},
|
||||
"termsAndConditions": {
|
||||
"agree": "Eu concordo!",
|
||||
"newTermsAndConditions": "Novos Termos e Condições",
|
||||
"termsAndConditionsConfirmed": "Eu li e confirmei os Terms and Conditions.",
|
||||
"termsAndConditionsNewConfirm": "Eu li e concordo com os novos termos de condições.",
|
||||
"termsAndConditionsNewConfirmText": "Por favor, leia os novos termos de uso agora!"
|
||||
},
|
||||
|
||||
@ -116,16 +116,6 @@
|
||||
"versus": "Против"
|
||||
},
|
||||
"components": {
|
||||
"enter-nonce": {
|
||||
"form": {
|
||||
"description": "Откройте папку \\\"Входящие\\\" и введите код из сообщения.",
|
||||
"next": "Продолжить",
|
||||
"nonce": "Введите код",
|
||||
"validations": {
|
||||
"length": "длина должна быть 6 символов"
|
||||
}
|
||||
}
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "Смена пароля не удалась. Может быть, код безопасности был неправильным?",
|
||||
@ -148,18 +138,23 @@
|
||||
"success": "Учетная запись успешно создана!",
|
||||
"title": "Создать учетную запись"
|
||||
},
|
||||
"email-nonce": {
|
||||
"form": {
|
||||
"description": "Откройте папку \\\"Входящие\\\" и введите код из сообщения.",
|
||||
"next": "Продолжить",
|
||||
"nonce": "Введите код",
|
||||
"validations": {
|
||||
"length": "длина должна быть 5 символов"
|
||||
}
|
||||
}
|
||||
},
|
||||
"signup": {
|
||||
"form": {
|
||||
"data-privacy": "Я прочитал и понял Заявление о конфиденциальности",
|
||||
"description": "Для начала работы введите свой адрес электронной почты:",
|
||||
"errors": {
|
||||
"email-exists": "Уже есть учетная запись пользователя с этим адресом электронной почты!",
|
||||
"invalid-invitation-token": "Похоже, что приглашение уже было использовано. Ссылку из приглашения можно использовать только один раз."
|
||||
"email-exists": "Уже есть учетная запись пользователя с этим адресом электронной почты!"
|
||||
},
|
||||
"invitation-code": "Код приглашения: <b>{code}</b>",
|
||||
"minimum-age": "Мне 18 лет или более",
|
||||
"no-commercial": "У меня нет коммерческих намерений, и я не представляю коммерческое предприятие или организацию.",
|
||||
"no-political": "Я не от имени какой-либо партии или политической организации в сети.",
|
||||
"submit": "Создать учетную запись",
|
||||
"success": "Письмо со ссылкой для завершения регистрации было отправлено на <b> {email} </b>",
|
||||
"terms-and-condition": "Принимаю Условия и положения."
|
||||
@ -729,9 +724,7 @@
|
||||
}
|
||||
},
|
||||
"termsAndConditions": {
|
||||
"agree": "Я согласен(на)!",
|
||||
"newTermsAndConditions": "Новые условия и положения",
|
||||
"termsAndConditionsConfirmed": "Я прочитал(а) и подтверждаю Условия и положения.",
|
||||
"termsAndConditionsNewConfirm": "Я прочитал(а) и согласен(на) с новыми условиями.",
|
||||
"termsAndConditionsNewConfirmText": "Пожалуйста, ознакомьтесь с новыми условиями использования!"
|
||||
},
|
||||
|
||||
@ -22,5 +22,5 @@ export default async ({ store, env, route, redirect }) => {
|
||||
params.path = route.path
|
||||
}
|
||||
|
||||
return redirect('/registration/signup', params)
|
||||
return redirect('/registration', params)
|
||||
}
|
||||
|
||||
@ -35,9 +35,7 @@ export default {
|
||||
'password-reset-request',
|
||||
'password-reset-enter-nonce',
|
||||
'password-reset-change-password',
|
||||
'registration-signup',
|
||||
'registration-enter-nonce',
|
||||
'registration-create-user-account',
|
||||
'registration',
|
||||
'pages-slug',
|
||||
'terms-and-conditions',
|
||||
'code-of-conduct',
|
||||
|
||||
467
webapp/pages/registration.spec.js
Normal file
467
webapp/pages/registration.spec.js
Normal file
@ -0,0 +1,467 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import Registration from './registration.vue'
|
||||
import Vue from 'vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['client-only'] = '<span><slot /></span>'
|
||||
config.stubs['router-link'] = '<span><slot /></span>'
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
config.stubs['infinite-loading'] = '<span><slot /></span>'
|
||||
|
||||
describe('Registration', () => {
|
||||
let wrapper
|
||||
let Wrapper
|
||||
let mocks
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: (key) => key,
|
||||
$i18n: {
|
||||
locale: () => 'de',
|
||||
},
|
||||
$route: {
|
||||
query: {},
|
||||
},
|
||||
$env: {},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
Wrapper = () => {
|
||||
return mount(Registration, {
|
||||
mocks,
|
||||
localVue,
|
||||
})
|
||||
}
|
||||
|
||||
describe('no "PUBLIC_REGISTRATION" and no "INVITE_REGISTRATION"', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$env = {
|
||||
PUBLIC_REGISTRATION: false,
|
||||
INVITE_REGISTRATION: false,
|
||||
}
|
||||
})
|
||||
|
||||
it('no "method" query in URI show "RegistrationSlideNoPublic"', () => {
|
||||
mocks.$route.query = {}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.hc-empty').exists()).toBe(true)
|
||||
expect(wrapper.find('.enter-invite').exists()).toBe(false)
|
||||
expect(wrapper.find('.enter-email').exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('"method=invite-mail" in URI show "RegistrationSlideNonce"', () => {
|
||||
it('no "email" query in URI', () => {
|
||||
mocks.$route.query = { method: 'invite-mail' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-nonce').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('"email=user%40example.org" query in URI', () => {
|
||||
it('have email displayed', () => {
|
||||
mocks.$route.query = { method: 'invite-mail', email: 'user@example.org' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-nonce').text()).toContain('user@example.org')
|
||||
})
|
||||
|
||||
it('"nonce=64835" query in URI have nonce in input', async () => {
|
||||
mocks.$route.query = {
|
||||
method: 'invite-mail',
|
||||
email: 'user@example.org',
|
||||
nonce: '64835',
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
await Vue.nextTick()
|
||||
const form = wrapper.find('.enter-nonce')
|
||||
expect(form.vm.formData.nonce).toEqual('64835')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"method=invite-code" in URI show "RegistrationSlideNoPublic"', () => {
|
||||
it('no "inviteCode" query in URI', () => {
|
||||
mocks.$route.query = { method: 'invite-code' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.hc-empty').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('"inviteCode=AAAAAA" query in URI', () => {
|
||||
mocks.$route.query = { method: 'invite-code', inviteCode: 'AAAAAA' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.hc-empty').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('no "PUBLIC_REGISTRATION" but "INVITE_REGISTRATION"', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$env = {
|
||||
PUBLIC_REGISTRATION: false,
|
||||
INVITE_REGISTRATION: true,
|
||||
}
|
||||
})
|
||||
|
||||
it('no "method" query in URI show "RegistrationSlideInvite"', () => {
|
||||
mocks.$route.query = {}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-invite').exists()).toBe(true)
|
||||
expect(wrapper.find('.enter-email').exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('"method=invite-mail" in URI show "RegistrationSlideNonce"', () => {
|
||||
it('no "inviteCode" query in URI', () => {
|
||||
mocks.$route.query = { method: 'invite-mail' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-nonce').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('"email=user%40example.org" query in URI', () => {
|
||||
it('have email displayed', () => {
|
||||
mocks.$route.query = { method: 'invite-mail', email: 'user@example.org' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-nonce').text()).toContain('user@example.org')
|
||||
})
|
||||
|
||||
it('"nonce=64835" query in URI have nonce in input', async () => {
|
||||
mocks.$route.query = {
|
||||
method: 'invite-mail',
|
||||
email: 'user@example.org',
|
||||
nonce: '64835',
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
await Vue.nextTick()
|
||||
const form = wrapper.find('.enter-nonce')
|
||||
expect(form.vm.formData.nonce).toEqual('64835')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"method=invite-code" in URI show "RegistrationSlideInvite"', () => {
|
||||
it('no "inviteCode" query in URI', () => {
|
||||
mocks.$route.query = { method: 'invite-code' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-invite').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('"inviteCode=AAAAAA" query in URI have invite code in input', async () => {
|
||||
mocks.$route.query = { method: 'invite-code', inviteCode: 'AAAAAA' }
|
||||
wrapper = Wrapper()
|
||||
await Vue.nextTick()
|
||||
const form = wrapper.find('.enter-invite')
|
||||
expect(form.vm.formData.inviteCode).toEqual('AAAAAA')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"PUBLIC_REGISTRATION" but no "INVITE_REGISTRATION"', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$env = {
|
||||
PUBLIC_REGISTRATION: true,
|
||||
INVITE_REGISTRATION: false,
|
||||
}
|
||||
})
|
||||
|
||||
it('no "method" query in URI show "RegistrationSlideEmail"', () => {
|
||||
mocks.$route.query = {}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-email').exists()).toBe(true)
|
||||
expect(wrapper.find('.enter-invite').exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('"method=invite-mail" in URI show "RegistrationSlideNonce"', () => {
|
||||
it('no "inviteCode" query in URI', () => {
|
||||
mocks.$route.query = { method: 'invite-mail' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-nonce').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('"email=user%40example.org" query in URI', () => {
|
||||
it('have email displayed', () => {
|
||||
mocks.$route.query = { method: 'invite-mail', email: 'user@example.org' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-nonce').text()).toContain('user@example.org')
|
||||
})
|
||||
|
||||
it('"nonce=64835" query in URI have nonce in input', async () => {
|
||||
mocks.$route.query = {
|
||||
method: 'invite-mail',
|
||||
email: 'user@example.org',
|
||||
nonce: '64835',
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
await Vue.nextTick()
|
||||
const form = wrapper.find('.enter-nonce')
|
||||
expect(form.vm.formData.nonce).toEqual('64835')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"method=invite-code" in URI show "RegistrationSlideEmail"', () => {
|
||||
it('no "inviteCode" query in URI', () => {
|
||||
mocks.$route.query = { method: 'invite-code' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-email').exists()).toBe(true)
|
||||
expect(wrapper.find('.enter-invite').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"PUBLIC_REGISTRATION" and "INVITE_REGISTRATION"', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$env = {
|
||||
PUBLIC_REGISTRATION: true,
|
||||
INVITE_REGISTRATION: true,
|
||||
}
|
||||
})
|
||||
|
||||
it('no "method" query in URI show "RegistrationSlideEmail"', () => {
|
||||
mocks.$route.query = {}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-email').exists()).toBe(true)
|
||||
expect(wrapper.find('.enter-invite').exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('"method=invite-mail" in URI show "RegistrationSlideNonce"', () => {
|
||||
it('no "inviteCode" query in URI', () => {
|
||||
mocks.$route.query = { method: 'invite-mail' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-nonce').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('"email=user%40example.org" query in URI', () => {
|
||||
it('have email displayed', () => {
|
||||
mocks.$route.query = { method: 'invite-mail', email: 'user@example.org' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-nonce').text()).toContain('user@example.org')
|
||||
})
|
||||
|
||||
it('"nonce=64835" query in URI have nonce in input', async () => {
|
||||
mocks.$route.query = {
|
||||
method: 'invite-mail',
|
||||
email: 'user@example.org',
|
||||
nonce: '64835',
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
await Vue.nextTick()
|
||||
const form = wrapper.find('.enter-nonce')
|
||||
expect(form.vm.formData.nonce).toEqual('64835')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('"method=invite-code" in URI show "RegistrationSlideInvite"', () => {
|
||||
it('no "inviteCode" query in URI', () => {
|
||||
mocks.$route.query = { method: 'invite-code' }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.enter-invite').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('"inviteCode=AAAAAA" query in URI have invite code in input', async () => {
|
||||
mocks.$route.query = { method: 'invite-code', inviteCode: 'AAAAAA' }
|
||||
wrapper = Wrapper()
|
||||
await Vue.nextTick()
|
||||
const form = wrapper.find('.enter-invite')
|
||||
expect(form.vm.formData.inviteCode).toEqual('AAAAAA')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// copied from webapp/components/Registration/Signup.spec.js as testing template
|
||||
// describe('with invitation code', () => {
|
||||
// let action
|
||||
// beforeEach(() => {
|
||||
// propsData.token = '12345'
|
||||
// action = async () => {
|
||||
// wrapper = Wrapper()
|
||||
// wrapper.find('input#email').setValue('mail@example.org')
|
||||
// await wrapper.find('form').trigger('submit')
|
||||
// await wrapper.html()
|
||||
// }
|
||||
// })
|
||||
|
||||
// describe('submit', () => {
|
||||
// it('delivers invitation code to backend', async () => {
|
||||
// await action()
|
||||
// const expected = expect.objectContaining({
|
||||
// mutation: SignupMutation,
|
||||
// variables: { email: 'mail@example.org', inviteCode: '12345' },
|
||||
// })
|
||||
// expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
// })
|
||||
|
||||
// describe('in case a user account with the email already exists', () => {
|
||||
// beforeEach(() => {
|
||||
// mocks.$apollo.mutate = jest
|
||||
// .fn()
|
||||
// .mockRejectedValue(
|
||||
// new Error('UserInputError: A user account with this email already exists.'),
|
||||
// )
|
||||
// })
|
||||
|
||||
// it('explains the error', async () => {
|
||||
// await action()
|
||||
// expect(mocks.$t).toHaveBeenCalledWith(
|
||||
// 'components.registration.signup.form.errors.email-exists',
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
})
|
||||
})
|
||||
|
||||
// template from deleted webapp/components/Registration/CreateUserAccount.spec.js
|
||||
// import { config, mount } from '@vue/test-utils'
|
||||
// import Vue from 'vue'
|
||||
// import { VERSION } from '~/constants/terms-and-conditions-version.js'
|
||||
// import CreateUserAccount from './CreateUserAccount'
|
||||
// import { SignupVerificationMutation } from '~/graphql/Registration.js'
|
||||
// const localVue = global.localVue
|
||||
|
||||
// config.stubs['sweetalert-icon'] = '<span><slot /></span>'
|
||||
// config.stubs['client-only'] = '<span><slot /></span>'
|
||||
// config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
|
||||
// describe('CreateUserAccount', () => {
|
||||
// let wrapper, Wrapper, mocks, propsData, stubs
|
||||
|
||||
// beforeEach(() => {
|
||||
// mocks = {
|
||||
// $toast: {
|
||||
// success: jest.fn(),
|
||||
// error: jest.fn(),
|
||||
// },
|
||||
// $t: jest.fn(),
|
||||
// $apollo: {
|
||||
// loading: false,
|
||||
// mutate: jest.fn(),
|
||||
// },
|
||||
// $i18n: {
|
||||
// locale: () => 'en',
|
||||
// },
|
||||
// }
|
||||
// propsData = {}
|
||||
// stubs = {
|
||||
// LocaleSwitch: "<div class='stub'></div>",
|
||||
// }
|
||||
// })
|
||||
|
||||
// describe('mount', () => {
|
||||
// Wrapper = () => {
|
||||
// return mount(CreateUserAccount, {
|
||||
// mocks,
|
||||
// propsData,
|
||||
// localVue,
|
||||
// stubs,
|
||||
// })
|
||||
// }
|
||||
|
||||
// describe('given email and nonce', () => {
|
||||
// beforeEach(() => {
|
||||
// propsData.nonce = '666777'
|
||||
// propsData.email = 'sixseven@example.org'
|
||||
// })
|
||||
|
||||
// it('renders a form to create a new user', () => {
|
||||
// wrapper = Wrapper()
|
||||
// expect(wrapper.find('.create-user-account').exists()).toBe(true)
|
||||
// })
|
||||
|
||||
// describe('submit', () => {
|
||||
// let action
|
||||
// beforeEach(() => {
|
||||
// action = async () => {
|
||||
// wrapper = Wrapper()
|
||||
// wrapper.find('input#name').setValue('John Doe')
|
||||
// wrapper.find('input#password').setValue('hellopassword')
|
||||
// wrapper.find('textarea#about').setValue('Hello I am the `about` attribute')
|
||||
// wrapper.find('input#passwordConfirmation').setValue('hellopassword')
|
||||
// wrapper.find('input#checkbox0').setChecked()
|
||||
// wrapper.find('input#checkbox1').setChecked()
|
||||
// wrapper.find('input#checkbox2').setChecked()
|
||||
// wrapper.find('input#checkbox3').setChecked()
|
||||
// wrapper.find('input#checkbox4').setChecked()
|
||||
// await wrapper.find('form').trigger('submit')
|
||||
// await wrapper.html()
|
||||
// }
|
||||
// })
|
||||
|
||||
// it('delivers data to backend', async () => {
|
||||
// await action()
|
||||
// const expected = expect.objectContaining({
|
||||
// variables: {
|
||||
// about: 'Hello I am the `about` attribute',
|
||||
// name: 'John Doe',
|
||||
// email: 'sixseven@example.org',
|
||||
// nonce: '666777',
|
||||
// password: 'hellopassword',
|
||||
// termsAndConditionsAgreedVersion: VERSION,
|
||||
// locale: 'en',
|
||||
// },
|
||||
// })
|
||||
// expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
// })
|
||||
|
||||
// it('calls CreateUserAccount graphql mutation', async () => {
|
||||
// await action()
|
||||
// const expected = expect.objectContaining({ mutation: SignupVerificationMutation })
|
||||
// expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
// })
|
||||
|
||||
// describe('in case mutation resolves', () => {
|
||||
// beforeEach(() => {
|
||||
// mocks.$apollo.mutate = jest.fn().mockResolvedValue({
|
||||
// data: {
|
||||
// SignupVerification: {
|
||||
// id: 'u1',
|
||||
// name: 'John Doe',
|
||||
// slug: 'john-doe',
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// })
|
||||
|
||||
// it('displays success', async () => {
|
||||
// await action()
|
||||
// await Vue.nextTick()
|
||||
// expect(mocks.$t).toHaveBeenCalledWith(
|
||||
// 'components.registration.create-user-account.success',
|
||||
// )
|
||||
// })
|
||||
|
||||
// describe('after timeout', () => {
|
||||
// beforeEach(jest.useFakeTimers)
|
||||
|
||||
// it('emits `userCreated` with { password, email }', async () => {
|
||||
// await action()
|
||||
// jest.runAllTimers()
|
||||
// expect(wrapper.emitted('userCreated')).toEqual([
|
||||
// [
|
||||
// {
|
||||
// email: 'sixseven@example.org',
|
||||
// password: 'hellopassword',
|
||||
// },
|
||||
// ],
|
||||
// ])
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('in case mutation rejects', () => {
|
||||
// beforeEach(() => {
|
||||
// mocks.$apollo.mutate = jest.fn().mockRejectedValue(new Error('Invalid nonce'))
|
||||
// })
|
||||
|
||||
// it('displays form errors', async () => {
|
||||
// await action()
|
||||
// await Vue.nextTick()
|
||||
// expect(mocks.$t).toHaveBeenCalledWith(
|
||||
// 'components.registration.create-user-account.error',
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
@ -1,33 +1,33 @@
|
||||
<template>
|
||||
<ds-container width="small">
|
||||
<base-card>
|
||||
<template #imageColumn>
|
||||
<a :href="links.ORGANIZATION" :title="$t('login.moreInfo', metadata)" target="_blank">
|
||||
<img class="image" alt="Sign up" src="/img/custom/sign-up.svg" />
|
||||
</a>
|
||||
</template>
|
||||
<nuxt-child />
|
||||
<template #topMenu>
|
||||
<locale-switch offset="5" />
|
||||
</template>
|
||||
</base-card>
|
||||
</ds-container>
|
||||
<registration-slider
|
||||
:registrationType="registrationType"
|
||||
:overwriteSliderData="overwriteSliderData"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import links from '~/constants/links.js'
|
||||
import metadata from '~/constants/metadata.js'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||
import RegistrationSlider from '~/components/Registration/RegistrationSlider'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LocaleSwitch,
|
||||
},
|
||||
layout: 'no-header',
|
||||
name: 'Registration',
|
||||
components: {
|
||||
RegistrationSlider,
|
||||
},
|
||||
data() {
|
||||
const { method = null, email = null, inviteCode = null, nonce = null } = this.$route.query
|
||||
return {
|
||||
metadata,
|
||||
links,
|
||||
method,
|
||||
overwriteSliderData: {
|
||||
collectedInputData: {
|
||||
inviteCode,
|
||||
email,
|
||||
emailSend: !!email,
|
||||
nonce,
|
||||
},
|
||||
},
|
||||
publicRegistration: this.$env.PUBLIC_REGISTRATION === true, // for 'false' in .env PUBLIC_REGISTRATION is of type undefined and not(!) boolean false, because of internal handling
|
||||
inviteRegistration: this.$env.INVITE_REGISTRATION === true, // for 'false' in .env INVITE_REGISTRATION is of type undefined and not(!) boolean false, because of internal handling
|
||||
}
|
||||
},
|
||||
asyncData({ store, redirect }) {
|
||||
@ -35,11 +35,24 @@ export default {
|
||||
redirect('/')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
registrationType() {
|
||||
if (!this.method) {
|
||||
return (
|
||||
(this.publicRegistration && 'public-registration') ||
|
||||
(this.inviteRegistration && 'invite-code') ||
|
||||
'no-public-registration'
|
||||
)
|
||||
} else {
|
||||
if (
|
||||
this.method === 'invite-mail' ||
|
||||
(this.method === 'invite-code' && this.inviteRegistration)
|
||||
) {
|
||||
return this.method
|
||||
}
|
||||
return this.publicRegistration ? 'public-registration' : 'no-public-registration'
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
<template>
|
||||
<create-user-account @userCreated="handleUserCreated" :email="email" :nonce="nonce" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CreateUserAccount from '~/components/Registration/CreateUserAccount'
|
||||
export default {
|
||||
data() {
|
||||
const { nonce = '', email = '' } = this.$route.query
|
||||
return { nonce, email }
|
||||
},
|
||||
components: {
|
||||
CreateUserAccount,
|
||||
},
|
||||
methods: {
|
||||
async handleUserCreated({ email, password }) {
|
||||
try {
|
||||
await this.$store.dispatch('auth/login', { email, password })
|
||||
this.$toast.success('You are logged in!')
|
||||
this.$router.push('/')
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,25 +0,0 @@
|
||||
<template>
|
||||
<enter-nonce :email="email" @nonceEntered="nonceEntered">
|
||||
<ds-space margin-bottom="xxx-small" margin-top="large" centered>
|
||||
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
|
||||
</ds-space>
|
||||
</enter-nonce>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EnterNonce from '~/components/EnterNonce/EnterNonce.vue'
|
||||
export default {
|
||||
components: {
|
||||
EnterNonce,
|
||||
},
|
||||
data() {
|
||||
const { email = '' } = this.$route.query
|
||||
return { email }
|
||||
},
|
||||
methods: {
|
||||
nonceEntered({ email, nonce }) {
|
||||
this.$router.push({ path: 'create-user-account', query: { email, nonce } })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,34 +0,0 @@
|
||||
<template>
|
||||
<signup v-if="publicRegistration" :invitation="false" @submit="handleSubmitted">
|
||||
<ds-space centered margin-top="large">
|
||||
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
|
||||
</ds-space>
|
||||
</signup>
|
||||
<ds-space v-else centered>
|
||||
<hc-empty icon="events" :message="$t('components.registration.signup.unavailable')" />
|
||||
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
|
||||
</ds-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Signup from '~/components/Registration/Signup'
|
||||
import HcEmpty from '~/components/Empty/Empty'
|
||||
|
||||
export default {
|
||||
layout: 'no-header',
|
||||
components: {
|
||||
HcEmpty,
|
||||
Signup,
|
||||
},
|
||||
asyncData({ app }) {
|
||||
return {
|
||||
publicRegistration: app.$env.PUBLIC_REGISTRATION,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSubmitted({ email }) {
|
||||
this.$router.push({ path: 'enter-nonce', query: { email } })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
Loading…
x
Reference in New Issue
Block a user