Merge pull request #1814 from Human-Connection/1785-register_as_new_user

Implement public registration
This commit is contained in:
mattwr18 2019-10-11 01:25:44 +02:00 committed by GitHub
commit 7430fdffa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 680 additions and 528 deletions

View File

@ -17,3 +17,4 @@ PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
SENTRY_DSN_BACKEND= SENTRY_DSN_BACKEND=
COMMIT= COMMIT=
PUBLIC_REGISTRATION=false

View File

@ -21,7 +21,12 @@ const {
GRAPHQL_URI = 'http://localhost:4000', GRAPHQL_URI = 'http://localhost:4000',
} = process.env } = process.env
export const requiredConfigs = { MAPBOX_TOKEN, JWT_SECRET, PRIVATE_KEY_PASSPHRASE } export const requiredConfigs = {
MAPBOX_TOKEN,
JWT_SECRET,
PRIVATE_KEY_PASSPHRASE,
}
export const smtpConfigs = { export const smtpConfigs = {
SMTP_HOST, SMTP_HOST,
SMTP_PORT, SMTP_PORT,
@ -30,7 +35,12 @@ export const smtpConfigs = {
SMTP_PASSWORD, SMTP_PASSWORD,
} }
export const neo4jConfigs = { NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD } export const neo4jConfigs = { NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD }
export const serverConfigs = { GRAPHQL_PORT, CLIENT_URI, GRAPHQL_URI } export const serverConfigs = {
GRAPHQL_PORT,
CLIENT_URI,
GRAPHQL_URI,
PUBLIC_REGISTRATION: process.env.PUBLIC_REGISTRATION === 'true',
}
export const developmentConfigs = { export const developmentConfigs = {
DEBUG: process.env.NODE_ENV !== 'production' && process.env.DEBUG, DEBUG: process.env.NODE_ENV !== 'production' && process.env.DEBUG,

View File

@ -18,7 +18,7 @@ export const signupTemplate = ({ email, nonce }) => {
subject, subject,
html: mustache.render( html: mustache.render(
templates.layout, templates.layout,
{ actionUrl, supportUrl, subject }, { actionUrl, nonce, supportUrl, subject },
{ content: templates.signup }, { content: templates.signup },
), ),
} }

View File

@ -60,7 +60,9 @@
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"> <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr> <tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;"> <td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Falls Du Dich nicht selbst bei <a href="https://human-connection.org" <p style="margin: 0;">Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0;">Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert hast.</p>
<p style="margin: 0; margin-top: 10px;">Falls Du Dich nicht selbst bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> angemeldet hast, schau doch mal vorbei! style="color: #17b53e;">Human Connection</a> angemeldet hast, schau doch mal vorbei!
Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.</p> Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.</p>
<p style="margin: 0; margin-top: 10px;">PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese <p style="margin: 0; margin-top: 10px;">PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese
@ -169,7 +171,9 @@
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"> <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr> <tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;"> <td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If you didn't sign up for <a href="https://human-connection.org" <p style="margin: 0;">If the above button doesn't work, you can also copy the following code into your browser window: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0;">However, this only works if you have registered through our website.</p>
<p style="margin: 0; margin-top: 10px;">If you didn't sign up for <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> we recommend you to check it out! style="color: #17b53e;">Human Connection</a> we recommend you to check it out!
It's a social network from people for people who want to connect and change the world together.</p> It's a social network from people for people who want to connect and change the world together.</p>
<p style="margin: 0; margin-top: 10px;">PS: If you ignore this e-mail we will not create an account <p style="margin: 0; margin-top: 10px;">PS: If you ignore this e-mail we will not create an account

View File

@ -111,6 +111,8 @@ const noEmailFilter = rule({
return !('email' in args) return !('email' in args)
}) })
const publicRegistration = rule()(() => !!CONFIG.PUBLIC_REGISTRATION)
// Permissions // Permissions
const permissions = shield( const permissions = shield(
{ {
@ -137,7 +139,7 @@ const permissions = shield(
'*': deny, '*': deny,
login: allow, login: allow,
SignupByInvitation: allow, SignupByInvitation: allow,
Signup: isAdmin, Signup: or(publicRegistration, isAdmin),
SignupVerification: allow, SignupVerification: allow,
CreateInvitationCode: and(isAuthenticated, or(not(invitationLimitReached), isAdmin)), CreateInvitationCode: and(isAuthenticated, or(not(invitationLimitReached), isAdmin)),
UpdateUser: onlyYourself, UpdateUser: onlyYourself,

View File

@ -15,6 +15,7 @@ services:
- ./webapp:/nitro-web - ./webapp:/nitro-web
environment: environment:
- NUXT_BUILD=/tmp/nuxt # avoid file permission issues when `rm -rf .nuxt/` - NUXT_BUILD=/tmp/nuxt # avoid file permission issues when `rm -rf .nuxt/`
- PUBLIC_REGISTRATION=true
command: yarn run dev command: yarn run dev
backend: backend:
build: build:
@ -28,6 +29,7 @@ services:
- SMTP_PORT=25 - SMTP_PORT=25
- SMTP_IGNORE_TLS=true - SMTP_IGNORE_TLS=true
- "DEBUG=${DEBUG}" - "DEBUG=${DEBUG}"
- PUBLIC_REGISTRATION=true
maintenance: maintenance:
image: humanconnection/maintenance:latest image: humanconnection/maintenance:latest
build: build:

View File

@ -1,3 +1,4 @@
MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ" MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ"
SENTRY_DSN_WEBAPP= SENTRY_DSN_WEBAPP=
COMMIT= COMMIT=
PUBLIC_REGISTRATION=false

View File

@ -1,12 +1,12 @@
import { mount, createLocalVue } from '@vue/test-utils' import { mount, createLocalVue } from '@vue/test-utils'
import VerifyNonce from './VerifyNonce.vue' import EnterNonce from './EnterNonce.vue'
import Styleguide from '@human-connection/styleguide' import Styleguide from '@human-connection/styleguide'
const localVue = createLocalVue() const localVue = createLocalVue()
localVue.use(Styleguide) localVue.use(Styleguide)
describe('VerifyNonce ', () => { describe('EnterNonce ', () => {
let wrapper let wrapper
let Wrapper let Wrapper
let mocks let mocks
@ -25,28 +25,28 @@ describe('VerifyNonce ', () => {
beforeEach(jest.useFakeTimers) beforeEach(jest.useFakeTimers)
Wrapper = () => { Wrapper = () => {
return mount(VerifyNonce, { return mount(EnterNonce, {
mocks, mocks,
localVue, localVue,
propsData, propsData,
}) })
} }
it('renders a verify nonce form', () => { it('renders an enter nonce form', () => {
wrapper = Wrapper() wrapper = Wrapper()
expect(wrapper.find('.verify-nonce').exists()).toBe(true) expect(wrapper.find('form').exists()).toBe(true)
}) })
describe('after verification nonce given', () => { describe('after nonce entered', () => {
beforeEach(() => { beforeEach(() => {
wrapper = Wrapper() wrapper = Wrapper()
wrapper.find('input#nonce').setValue('123456') wrapper.find('input#nonce').setValue('123456')
wrapper.find('form').trigger('submit') wrapper.find('form').trigger('submit')
}) })
it('emits `verification`', () => { it('emits `nonceEntered`', () => {
const expected = [[{ nonce: '123456', email: 'mail@example.org' }]] const expected = [[{ nonce: '123456', email: 'mail@example.org' }]]
expect(wrapper.emitted('verification')).toEqual(expected) expect(wrapper.emitted('nonceEntered')).toEqual(expected)
}) })
}) })
}) })

View File

@ -0,0 +1,66 @@
<template>
<ds-space margin-top="large" margin-bottom="xxx-small">
<ds-form
v-model="formData"
:schema="formSchema"
@submit="handleSubmitVerify"
@input="handleInput"
@input-valid="handleInputValid"
>
<ds-input
:placeholder="$t('components.enter-nonce.form.nonce')"
model="nonce"
name="nonce"
id="nonce"
icon="question-circle"
/>
<ds-space margin-botton="large">
<ds-text>
{{ $t('components.enter-nonce.form.description') }}
</ds-text>
</ds-space>
<ds-button :disabled="disabled" primary fullwidth name="submit" type="submit">
{{ $t('components.enter-nonce.form.next') }}
</ds-button>
</ds-form>
<slot></slot>
</ds-space>
</template>
<script>
export default {
props: {
email: { type: String, required: true },
},
data() {
return {
formData: {
nonce: '',
},
formSchema: {
nonce: {
type: 'string',
min: 6,
max: 6,
required: true,
message: this.$t('components.enter-nonce.form.validations.length'),
},
},
disabled: true,
}
},
methods: {
async handleInput() {
this.disabled = true
},
async handleInputValid() {
this.disabled = false
},
handleSubmitVerify() {
const { nonce } = this.formData
const email = this.email
this.$emit('nonceEntered', { email, nonce })
},
},
}
</script>

View File

@ -39,7 +39,7 @@ describe('ChangePassword ', () => {
}) })
} }
describe('given email and verification nonce', () => { describe('given email and nonce', () => {
beforeEach(() => { beforeEach(() => {
propsData.email = 'mail@example.org' propsData.email = 'mail@example.org'
propsData.nonce = '123456' propsData.nonce = '123456'
@ -66,7 +66,7 @@ describe('ChangePassword ', () => {
describe('password reset successful', () => { describe('password reset successful', () => {
it('displays success message', () => { it('displays success message', () => {
const expected = 'verify-nonce.form.change-password.success' const expected = 'components.password-reset.change-password.success'
expect(mocks.$t).toHaveBeenCalledWith(expected) expect(mocks.$t).toHaveBeenCalledWith(expected)
}) })

View File

@ -1,54 +1,62 @@
<template> <template>
<ds-card class="verify-nonce"> <ds-space margin-top="base" margin-bottom="xxx-small">
<ds-space margin="large"> <ds-form
<ds-form v-if="!changePasswordResult"
v-if="!changePasswordResult" v-model="formData"
v-model="formData" :schema="formSchema"
:schema="formSchema" @submit="handleSubmitPassword"
@submit="handleSubmitPassword" class="change-password"
class="change-password" >
> <template slot-scope="{ errors }">
<template slot-scope="{ errors }"> <ds-input
<ds-input id="password"
id="password" model="password"
model="password" type="password"
type="password" autocomplete="off"
autocomplete="off" :label="$t('settings.security.change-password.label-new-password')"
:label="$t('settings.security.change-password.label-new-password')" />
/> <ds-input
<ds-input id="passwordConfirmation"
id="passwordConfirmation" model="passwordConfirmation"
model="passwordConfirmation" type="password"
type="password" autocomplete="off"
autocomplete="off" :label="$t('settings.security.change-password.label-new-password-confirm')"
:label="$t('settings.security.change-password.label-new-password-confirm')" />
/> <password-strength :password="formData.password" />
<password-strength :password="formData.password" /> <ds-space margin-top="base" margin-bottom="xxx-small">
<ds-space margin-top="base"> <ds-button :loading="$apollo.loading" :disabled="errors" primary>
<ds-button :loading="$apollo.loading" :disabled="errors" primary> {{ $t('settings.security.change-password.button') }}
{{ $t('settings.security.change-password.button') }} </ds-button>
</ds-button> </ds-space>
</ds-space> </template>
</template> </ds-form>
</ds-form> <ds-space v-else>
<ds-text v-else> <template v-if="changePasswordResult === 'success'">
<template v-if="changePasswordResult === 'success'"> <transition name="ds-transition-fade">
<sweetalert-icon icon="success" /> <sweetalert-icon icon="success" />
<ds-text> </transition>
{{ $t(`verify-nonce.form.change-password.success`) }} <ds-text>
</ds-text> {{ $t('components.password-reset.change-password.success') }}
</template> </ds-text>
<template v-else> </template>
<template v-else>
<transition name="ds-transition-fade">
<sweetalert-icon icon="error" /> <sweetalert-icon icon="error" />
<ds-text align="left"> </transition>
{{ $t(`verify-nonce.form.change-password.error`) }} <ds-text>
{{ $t('verify-nonce.form.change-password.help') }} <p>
</ds-text> {{ $t(`components.password-reset.change-password.error`) }}
<a href="mailto:support@human-connection.org">support@human-connection.org</a> </p>
</template> <p>
</ds-text> {{ $t('components.password-reset.change-password.help') }}
<br />
<a href="mailto:support@human-connection.org">support@human-connection.org</a>
</p>
</ds-text>
</template>
<slot></slot>
</ds-space> </ds-space>
</ds-card> </ds-space>
</template> </template>
<script> <script>

View File

@ -18,7 +18,7 @@ describe('Request', () => {
success: jest.fn(), success: jest.fn(),
error: jest.fn(), error: jest.fn(),
}, },
$t: jest.fn(), $t: jest.fn(t => t),
$apollo: { $apollo: {
loading: false, loading: false,
mutate: jest.fn().mockResolvedValue({ data: { reqestPasswordReset: true } }), mutate: jest.fn().mockResolvedValue({ data: { reqestPasswordReset: true } }),
@ -45,7 +45,7 @@ describe('Request', () => {
it('renders a password reset form', () => { it('renders a password reset form', () => {
wrapper = Wrapper() wrapper = Wrapper()
expect(wrapper.find('.password-reset').exists()).toBe(true) expect(wrapper.find('form').exists()).toBe(true)
}) })
describe('submit', () => { describe('submit', () => {
@ -69,7 +69,10 @@ describe('Request', () => {
}) })
it('displays a message that a password email was requested', () => { it('displays a message that a password email was requested', () => {
const expected = ['password-reset.form.submitted', { email: 'mail@example.org' }] const expected = [
'components.password-reset.request.form.submitted',
{ email: 'mail@example.org' },
]
expect(mocks.$t).toHaveBeenCalledWith(...expected) expect(mocks.$t).toHaveBeenCalledWith(...expected)
}) })

View File

@ -1,77 +1,55 @@
<template> <template>
<ds-card class="password-reset"> <ds-form
<ds-flex gutter="small"> v-if="!submitted"
<ds-flex-item :width="{ base: '100%', sm: '50%' }" centered> @input="handleInput"
<client-only> @input-valid="handleInputValid"
<locale-switch class="login-locale-switch" offset="5" /> v-model="formData"
</client-only> :schema="formSchema"
<ds-space margin-top="small" margin-bottom="xxx-small" centered> @submit="handleSubmit"
<img class="login-image" alt="Human Connection" src="/img/sign-up/humanconnection.svg" /> >
</ds-space> <ds-space margin="small">
</ds-flex-item> <ds-input
<ds-flex-item :width="{ base: '100%', sm: '50%' }" centered> :placeholder="$t('login.email')"
<ds-space margin="small"> type="email"
<ds-text size="small" align="left">{{ $t('login.copy') }}</ds-text> id="email"
</ds-space> model="email"
<ds-form name="email"
v-if="!submitted" icon="envelope"
@input="handleInput" />
@input-valid="handleInputValid" </ds-space>
v-model="formData" <ds-space margin-botton="large">
:schema="formSchema" <ds-text align="left">{{ $t('components.password-reset.request.form.description') }}</ds-text>
@submit="handleSubmit" </ds-space>
> <ds-button
<ds-input :disabled="disabled"
:placeholder="$t('login.email')" :loading="$apollo.loading"
type="email" primary
id="email" fullwidth
model="email" name="submit"
name="email" type="submit"
icon="envelope" icon="envelope"
/> >
<ds-space margin-botton="large"> {{ $t('components.password-reset.request.form.submit') }}
<ds-text align="left">{{ $t('password-reset.form.description') }}</ds-text> </ds-button>
</ds-space> <slot></slot>
<ds-button </ds-form>
:disabled="disabled" <div v-else>
:loading="$apollo.loading" <transition name="ds-transition-fade">
primary <ds-flex centered>
fullwidth <sweetalert-icon icon="info" />
name="submit" </ds-flex>
type="submit" </transition>
icon="envelope" <ds-text v-html="submitMessage" align="left" />
> </div>
{{ $t('password-reset.form.submit') }}
</ds-button>
</ds-form>
<div v-else>
<transition name="ds-transition-fade">
<ds-flex centered>
<sweetalert-icon icon="info" />
</ds-flex>
</transition>
<ds-text v-html="submitMessage" align="left" />
</div>
<ds-space margin-bottom="small" />
<div>
<nuxt-link to="/login">{{ $t('site.login') }}</nuxt-link>
</div>
</ds-flex-item>
</ds-flex>
<ds-space margin="x-small"></ds-space>
</ds-card>
</template> </template>
<script> <script>
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { SweetalertIcon } from 'vue-sweetalert-icons' import { SweetalertIcon } from 'vue-sweetalert-icons'
import LocaleSwitch from '../LocaleSwitch/LocaleSwitch'
export default { export default {
components: { components: {
SweetalertIcon, SweetalertIcon,
LocaleSwitch,
}, },
data() { data() {
return { return {
@ -92,7 +70,7 @@ export default {
computed: { computed: {
submitMessage() { submitMessage() {
const { email } = this.formData const { email } = this.formData
return this.$t('password-reset.form.submitted', { email }) return this.$t('components.password-reset.request.form.submitted', { email })
}, },
}, },
methods: { methods: {
@ -124,17 +102,3 @@ export default {
}, },
} }
</script> </script>
<style>
.login-image {
width: 90%;
max-width: 200px;
}
.password-reset {
position: relative;
}
.login-locale-switch {
position: absolute;
top: 1em;
left: 1em;
}
</style>

View File

@ -1,67 +0,0 @@
<template>
<ds-card class="verify-nonce">
<ds-space margin="large">
<ds-form
v-model="formData"
:schema="formSchema"
@submit="handleSubmitVerify"
@input="handleInput"
@input-valid="handleInputValid"
>
<ds-input
:placeholder="$t('verify-nonce.form.nonce')"
model="nonce"
name="nonce"
id="nonce"
icon="question-circle"
/>
<ds-space margin-botton="large">
<ds-text>
{{ $t('verify-nonce.form.description') }}
</ds-text>
</ds-space>
<ds-button :disabled="disabled" primary fullwidth name="submit" type="submit">
{{ $t('verify-nonce.form.next') }}
</ds-button>
</ds-form>
</ds-space>
</ds-card>
</template>
<script>
export default {
props: {
email: { type: String, required: true },
},
data() {
return {
formData: {
nonce: '',
},
formSchema: {
nonce: {
type: 'string',
min: 6,
max: 6,
required: true,
message: this.$t('common.validations.verification-nonce'),
},
},
disabled: true,
}
},
methods: {
async handleInput() {
this.disabled = true
},
async handleInputValid() {
this.disabled = false
},
handleSubmitVerify() {
const { nonce } = this.formData
const email = this.email
this.$emit('verification', { email, nonce })
},
},
}
</script>

View File

@ -8,6 +8,7 @@ const localVue = createLocalVue()
localVue.use(Styleguide) localVue.use(Styleguide)
config.stubs['sweetalert-icon'] = '<span><slot /></span>' config.stubs['sweetalert-icon'] = '<span><slot /></span>'
config.stubs['client-only'] = '<span><slot /></span>' config.stubs['client-only'] = '<span><slot /></span>'
config.stubs['nuxt-link'] = '<span><slot /></span>'
describe('CreateUserAccount', () => { describe('CreateUserAccount', () => {
let wrapper, Wrapper, mocks, propsData, stubs let wrapper, Wrapper, mocks, propsData, stubs
@ -102,7 +103,9 @@ describe('CreateUserAccount', () => {
it('displays success', async () => { it('displays success', async () => {
await action() await action()
expect(mocks.$t).toHaveBeenCalledWith('registration.create-user-account.success') expect(mocks.$t).toHaveBeenCalledWith(
'components.registration.create-user-account.success',
)
}) })
describe('after timeout', () => { describe('after timeout', () => {
@ -130,7 +133,9 @@ describe('CreateUserAccount', () => {
it('displays form errors', async () => { it('displays form errors', async () => {
await action() await action()
expect(wrapper.find('.backendErrors').text()).toContain('Invalid nonce') expect(mocks.$t).toHaveBeenCalledWith(
'components.registration.create-user-account.error',
)
}) })
}) })
}) })

View File

@ -1,117 +1,111 @@
<template> <template>
<ds-container width="small"> <div v-if="response === 'success'">
<ds-card v-if="success" class="success"> <transition name="ds-transition-fade">
<ds-space> <sweetalert-icon icon="success" />
<sweetalert-icon icon="success" /> </transition>
<ds-text align="center" bold color="success"> <ds-text align="center" bold color="success">
{{ $t('registration.create-user-account.success') }} {{ $t('components.registration.create-user-account.success') }}
</ds-text> </ds-text>
</ds-space> </div>
</ds-card> <div v-else-if="response === 'error'">
<ds-card v-else class="create-account-card"> <transition name="ds-transition-fade">
<client-only> <sweetalert-icon icon="error" />
<locale-switch /> </transition>
</client-only> <ds-text align="center" bold color="danger">
<ds-space centered> {{ $t('components.registration.create-user-account.error') }}
<img </ds-text>
class="create-account-image" <ds-text align="center">
alt="Create an account for Human Connection" {{ $t('components.registration.create-user-account.help') }}
src="/img/sign-up/nicetomeetyou.svg" <a :href="supportEmail.href">{{ supportEmail.label }}</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-space> <ds-input
<ds-space> id="about"
<ds-heading size="h3"> model="about"
{{ $t('registration.create-user-account.title') }} type="textarea"
</ds-heading> rows="3"
</ds-space> :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-form class="create-user-account" v-model="formData" :schema="formSchema" @submit="submit"> <ds-text>
<template v-slot="{ errors }"> <input
<ds-flex gutter="base"> id="checkbox"
<ds-flex-item width="100%"> type="checkbox"
<ds-input v-model="termsAndConditionsConfirmed"
id="name" :checked="termsAndConditionsConfirmed"
model="name" />
icon="user" <label
:label="$t('settings.data.labelName')" for="checkbox"
:placeholder="$t('settings.data.namePlaceholder')" v-html="$t('termsAndConditions.termsAndConditionsConfirmed')"
/> ></label>
<ds-input </ds-text>
id="about" <ds-button
model="about" style="float: right;"
type="textarea" icon="check"
rows="3" type="submit"
:label="$t('settings.data.labelBio')" :loading="$apollo.loading"
:placeholder="$t('settings.data.labelBio')" :disabled="errors || !termsAndConditionsConfirmed"
/> primary
<ds-input >
id="password" {{ $t('actions.save') }}
model="password" </ds-button>
type="password" </template>
autocomplete="off" </ds-form>
:label="$t('settings.security.change-password.label-new-password')" </div>
/>
<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="checkbox"
type="checkbox"
v-model="termsAndConditionsConfirmed"
:checked="termsAndConditionsConfirmed"
/>
<label
for="checkbox"
v-html="$t('termsAndConditions.termsAndConditionsConfirmed')"
></label>
</ds-text>
</ds-flex-item>
<ds-flex-item width="100%">
<ds-space class="backendErrors" v-if="backendErrors">
<ds-text align="center" bold color="danger">{{ backendErrors.message }}</ds-text>
</ds-space>
<ds-button
style="float: right;"
icon="check"
type="submit"
:loading="$apollo.loading"
:disabled="errors || !termsAndConditionsConfirmed"
primary
>
{{ $t('actions.save') }}
</ds-button>
</ds-flex-item>
</ds-flex>
</template>
</ds-form>
</ds-card>
</ds-container>
</template> </template>
<script> <script>
import PasswordStrength from '../Password/Strength' import PasswordStrength from '../Password/Strength'
import { SweetalertIcon } from 'vue-sweetalert-icons' import { SweetalertIcon } from 'vue-sweetalert-icons'
import PasswordForm from '~/components/utils/PasswordFormHelper' import PasswordForm from '~/components/utils/PasswordFormHelper'
import { SUPPORT_EMAIL } from '~/constants/emails.js'
import { VERSION } from '~/constants/terms-and-conditions-version.js' import { VERSION } from '~/constants/terms-and-conditions-version.js'
import { SignupVerificationMutation } from '~/graphql/Registration.js' import { SignupVerificationMutation } from '~/graphql/Registration.js'
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
export default { export default {
components: { components: {
PasswordStrength, PasswordStrength,
SweetalertIcon, SweetalertIcon,
LocaleSwitch,
}, },
data() { data() {
const passwordForm = PasswordForm({ translate: this.$t }) const passwordForm = PasswordForm({ translate: this.$t })
return { return {
supportEmail: SUPPORT_EMAIL,
formData: { formData: {
name: '', name: '',
about: '', about: '',
@ -130,8 +124,7 @@ export default {
...passwordForm.formSchema, ...passwordForm.formSchema,
}, },
disabled: true, disabled: true,
success: null, response: null,
backendErrors: null,
// TODO: Our styleguide does not support checkmarks. // TODO: Our styleguide does not support checkmarks.
// Integrate termsAndConditionsConfirmed into `this.formData` once we // Integrate termsAndConditionsConfirmed into `this.formData` once we
// have checkmarks available. // have checkmarks available.
@ -152,7 +145,7 @@ export default {
mutation: SignupVerificationMutation, mutation: SignupVerificationMutation,
variables: { name, password, about, email, nonce, termsAndConditionsAgreedVersion }, variables: { name, password, about, email, nonce, termsAndConditionsAgreedVersion },
}) })
this.success = true this.response = 'success'
setTimeout(() => { setTimeout(() => {
this.$emit('userCreated', { this.$emit('userCreated', {
email, email,
@ -160,7 +153,7 @@ export default {
}) })
}, 3000) }, 3000)
} catch (err) { } catch (err) {
this.backendErrors = err this.response = 'error'
} }
}, },
}, },

View File

@ -42,7 +42,7 @@ describe('Signup', () => {
describe('without invitation code', () => { describe('without invitation code', () => {
it('renders signup form', () => { it('renders signup form', () => {
wrapper = Wrapper() wrapper = Wrapper()
expect(wrapper.find('.signup').exists()).toBe(true) expect(wrapper.find('form').exists()).toBe(true)
}) })
describe('submit', () => { describe('submit', () => {
@ -69,15 +69,18 @@ describe('Signup', () => {
}) })
it('displays a message that a mail for email verification was sent', () => { it('displays a message that a mail for email verification was sent', () => {
const expected = ['registration.signup.form.success', { email: 'mail@example.org' }] const expected = [
'components.registration.signup.form.success',
{ email: 'mail@example.org' },
]
expect(mocks.$t).toHaveBeenCalledWith(...expected) expect(mocks.$t).toHaveBeenCalledWith(...expected)
}) })
describe('after animation', () => { describe('after animation', () => {
beforeEach(jest.runAllTimers) beforeEach(jest.runAllTimers)
it('emits `handleSubmitted`', () => { it('emits `submit`', () => {
expect(wrapper.emitted('handleSubmitted')).toEqual([[{ email: 'mail@example.org' }]]) expect(wrapper.emitted('submit')).toEqual([[{ email: 'mail@example.org' }]])
}) })
}) })
}) })
@ -121,7 +124,9 @@ describe('Signup', () => {
it('explains the error', async () => { it('explains the error', async () => {
await action() await action()
expect(mocks.$t).toHaveBeenCalledWith('registration.signup.form.errors.email-exists') expect(mocks.$t).toHaveBeenCalledWith(
'components.registration.signup.form.errors.email-exists',
)
}) })
}) })
@ -137,7 +142,7 @@ describe('Signup', () => {
it('explains the error', async () => { it('explains the error', async () => {
await action() await action()
expect(mocks.$t).toHaveBeenCalledWith( expect(mocks.$t).toHaveBeenCalledWith(
'registration.signup.form.errors.invalid-invitation-token', 'components.registration.signup.form.errors.invalid-invitation-token',
) )
}) })
}) })

View File

@ -1,59 +1,63 @@
<template> <template>
<ds-card class="signup"> <ds-space v-if="!success && !error" margin="large">
<ds-space margin="large"> <ds-form
<ds-form @input="handleInput"
v-if="!success && !error" @input-valid="handleInputValid"
@input="handleInput" v-model="formData"
@input-valid="handleInputValid" :schema="formSchema"
v-model="formData" @submit="handleSubmit"
:schema="formSchema" >
@submit="handleSubmit" <h1>
{{ invitation ? $t('profile.invites.title') : $t('components.registration.signup.title') }}
</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>
{{
invitation
? $t('profile.invites.description')
: $t('components.registration.signup.form.description')
}}
</ds-text>
</ds-space>
<ds-input
:placeholder="invitation ? $t('profile.invites.emailPlaceholder') : $t('login.email')"
type="email"
id="email"
model="email"
name="email"
icon="envelope"
/>
<ds-button
:disabled="disabled"
:loading="$apollo.loading"
primary
fullwidth
name="submit"
type="submit"
icon="envelope"
> >
<h1>{{ invitation ? $t('profile.invites.title') : $t('registration.signup.title') }}</h1> {{ $t('components.registration.signup.form.submit') }}
<ds-space v-if="token" margin-botton="large"> </ds-button>
<ds-text v-html="$t('registration.signup.form.invitation-code', { code: token })" /> <slot></slot>
</ds-space> </ds-form>
<ds-space margin-botton="large"> </ds-space>
<ds-text> <div v-else margin="large">
{{ <template v-if="!error">
invitation <transition name="ds-transition-fade">
? $t('profile.invites.description') <sweetalert-icon icon="info" />
: $t('registration.signup.form.description') </transition>
}} <ds-text align="center" v-html="submitMessage" />
</ds-text> </template>
</ds-space> <template v-else>
<ds-input <transition name="ds-transition-fade">
:placeholder="invitation ? $t('profile.invites.emailPlaceholder') : $t('login.email')" <sweetalert-icon icon="error" />
type="email" </transition>
id="email" <ds-text align="center">{{ error.message }}</ds-text>
model="email" </template>
name="email" </div>
icon="envelope"
/>
<ds-button
:disabled="disabled"
:loading="$apollo.loading"
primary
fullwidth
name="submit"
type="submit"
icon="envelope"
>
{{ $t('registration.signup.form.submit') }}
</ds-button>
</ds-form>
<div v-else>
<template v-if="!error">
<sweetalert-icon icon="info" />
<ds-text align="center" v-html="submitMessage" />
</template>
<template v-else>
<sweetalert-icon icon="error" />
<ds-text align="center">{{ error.message }}</ds-text>
</template>
</div>
</ds-space>
</ds-card>
</template> </template>
<script> <script>
@ -103,7 +107,7 @@ export default {
computed: { computed: {
submitMessage() { submitMessage() {
const { email } = this.formData const { email } = this.formData
return this.$t('registration.signup.form.success', { email }) return this.$t('components.registration.signup.form.success', { email })
}, },
}, },
methods: { methods: {
@ -123,7 +127,7 @@ export default {
this.success = true this.success = true
setTimeout(() => { setTimeout(() => {
this.$emit('handleSubmitted', { email }) this.$emit('submit', { email })
}, 3000) }, 3000)
} catch (err) { } catch (err) {
const { message } = err const { message } = err
@ -133,7 +137,10 @@ export default {
} }
for (const [pattern, key] of Object.entries(mapping)) { for (const [pattern, key] of Object.entries(mapping)) {
if (message.includes(pattern)) if (message.includes(pattern))
this.error = { key, message: this.$t(`registration.signup.form.errors.${key}`) } this.error = {
key,
message: this.$t(`components.registration.signup.form.errors.${key}`),
}
} }
if (!this.error) { if (!this.error) {
this.$toast.error(message) this.$toast.error(message)

View File

@ -0,0 +1,4 @@
export const SUPPORT_EMAIL = {
href: 'mailto:support@human-connection.org',
label: 'support@human-connection.org',
}

View File

@ -1,4 +1,52 @@
{ {
"components": {
"password-reset": {
"request": {
"form": {
"description": "Eine Mail zum Zurücksetzen des Passworts wird an die angegebene E-Mail Adresse geschickt.",
"submit": "Email anfordern",
"submitted": "Eine E-Mail mit weiteren Instruktionen wurde verschickt an <b>{email}</b>"
}
},
"change-password": {
"success": "Änderung des Passworts war erfolgreich!",
"error": "Passwort Änderung fehlgeschlagen. Möglicherweise falscher Sicherheitscode?",
"help": "Falls Probleme auftreten, schreib uns gerne eine Mail an:"
}
},
"enter-nonce": {
"form": {
"nonce": "Code eingeben",
"description": "Öffne dein E-Mail Postfach und gib den Code ein, den wir geschickt haben.",
"next": "Weiter",
"validations": {
"length": "muss genau 6 Buchstaben lang sein"
}
}
},
"registration": {
"signup": {
"unavailable": "Leider ist die öffentliche Registrierung von Benutzerkonten auf diesem Server derzeit nicht möglich.",
"title": "Mach mit bei Human Connection!",
"form": {
"description": "Um loszulegen, gib deine E-Mail Adresse ein:",
"invitation-code": "Dein Einladungscode lautet: <b>{code}</b>",
"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."
},
"submit": "Konto erstellen",
"success": "Eine Mail mit einem Bestätigungslink für die Registrierung wurde an <b>{email}</b> geschickt"
}
},
"create-user-account": {
"title": "Benutzerkonto anlegen",
"success": "Dein Benutzerkonto wurde erstellt!",
"error": "Es konnte kein Benutzerkonto erstellt werden!",
"help": "Vielleicht war der Bestätigungscode falsch oder abgelaufen? Wenn das Problem weiterhin besteht, schick uns gerne eine E-Mail an:"
}
}
},
"maintenance": { "maintenance": {
"title": "Human Connection befindet sich in der Wartung", "title": "Human Connection befindet sich in der Wartung",
"explanation": "Zurzeit führen wir einige geplante Wartungsarbeiten durch, bitte versuch es später erneut.", "explanation": "Zurzeit führen wir einige geplante Wartungsarbeiten durch, bitte versuch es später erneut.",
@ -42,7 +90,7 @@
"bank": "Bankverbindung", "bank": "Bankverbindung",
"germany": "Deutschland", "germany": "Deutschland",
"code-of-conduct": "Verhaltenscodex", "code-of-conduct": "Verhaltenscodex",
"login": "Zurück zum Anmeldung" "back-to-login": "Zurück zur Anmeldung"
}, },
"sorting": { "sorting": {
"newest": "Neueste", "newest": "Neueste",
@ -55,50 +103,14 @@
"email": "Deine E-Mail", "email": "Deine E-Mail",
"password": "Dein Passwort", "password": "Dein Passwort",
"forgotPassword": "Passwort vergessen?", "forgotPassword": "Passwort vergessen?",
"no-account": "Du hast noch keinen Benutzerkonto?",
"register": "Benutzerkonto erstellen",
"moreInfo": "Was ist Human Connection?", "moreInfo": "Was ist Human Connection?",
"moreInfoURL": "https://human-connection.org", "moreInfoURL": "https://human-connection.org",
"moreInfoHint": "zur Präsentationsseite", "moreInfoHint": "zur Präsentationsseite",
"hello": "Hallo", "hello": "Hallo",
"success": "Du bist eingeloggt!" "success": "Du bist eingeloggt!"
}, },
"password-reset": {
"form": {
"description": "Eine Mail zum Zurücksetzen des Passworts wird an die angegebene E-Mail Adresse geschickt.",
"submit": "Email anfordern",
"submitted": "Eine E-Mail mit weiteren Instruktionen wurde verschickt an <b>{email}</b>"
}
},
"registration": {
"signup": {
"title": "Mach mit bei Human Connection!",
"form": {
"description": "Um loszulegen, gib deine E-Mail Adresse ein:",
"invitation-code": "Dein Einladungscode lautet: <b>{code}</b>",
"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."
},
"submit": "Konto erstellen",
"success": "Eine Mail mit einem Bestätigungslink für die Registrierung wurde an <b>{email}</b> geschickt"
}
},
"create-user-account": {
"title": "Benutzerkonto anlegen",
"success": "Dein Benutzerkonto wurde erstellt!"
}
},
"verify-nonce": {
"form": {
"nonce": "Code eingeben",
"description": "Öffne dein E-Mail Postfach und gib den Code ein, den wir geschickt haben.",
"next": "Weiter",
"change-password": {
"success": "Änderung des Passworts war erfolgreich!",
"error": "Passwort Änderung fehlgeschlagen. Möglicherweise falscher Sicherheitscode?",
"help": "Falls Probleme auftreten, schreib uns gerne eine Mail an:"
}
}
},
"editor": { "editor": {
"placeholder": "Schreib etwas Inspirierendes …", "placeholder": "Schreib etwas Inspirierendes …",
"mention": { "mention": {
@ -400,8 +412,7 @@
"reportContent": "Melden", "reportContent": "Melden",
"validations": { "validations": {
"email": "muss eine gültige E-Mail Adresse sein", "email": "muss eine gültige E-Mail Adresse sein",
"url": "muss eine gültige URL sein", "url": "muss eine gültige URL sein"
"verification-nonce": "muss genau 6 Buchstaben lang sein"
} }
}, },
"actions": { "actions": {

View File

@ -1,4 +1,53 @@
{ {
"components": {
"password-reset": {
"request": {
"title": "Reset your password",
"form": {
"description": "A password reset email will be sent to the given email address.",
"submit": "Request email",
"submitted": "An email with further instructions has been sent to <b>{email}</b>"
}
},
"change-password": {
"success": "Changing your password was successful!",
"error": "Changing your password failed. Maybe the security code was not correct?",
"help": "In case of problems, feel free to ask for help by sending us a mail to:"
}
},
"enter-nonce": {
"form": {
"nonce": "Enter your code",
"description": "Open your inbox and enter the code that we've sent to you.",
"next": "Continue",
"validations": {
"length": "must be 6 characters long"
}
}
},
"registration": {
"signup": {
"unavailable": "Unfortunately, public registration of user accounts is not available right now on this server.",
"title": "Join Human Connection!",
"form": {
"description": "To get started, enter your email address:",
"invitation-code": "Your invitation code is: <b>{code}</b>",
"errors": {
"email-exists": "There is already a user account with this email address!",
"invalid-invitation-token": "It looks like as if the invitation has been used already. Invitation links can only be used once."
},
"submit": "Create an account",
"success": "A mail with a link to complete your registration has been sent to <b>{email}</b>"
}
},
"create-user-account": {
"title": "Create user account",
"success": "Your account has been created!",
"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:"
}
}
},
"maintenance": { "maintenance": {
"title": "Human Connection is under maintenance", "title": "Human Connection is under maintenance",
"explanation": "At the moment we are doing some scheduled maintenance, please try again later.", "explanation": "At the moment we are doing some scheduled maintenance, please try again later.",
@ -42,7 +91,7 @@
"bank": "bank account", "bank": "bank account",
"germany": "Germany", "germany": "Germany",
"code-of-conduct": "Code of Conduct", "code-of-conduct": "Code of Conduct",
"login": "Back to login" "back-to-login": "Back to login page"
}, },
"sorting": { "sorting": {
"newest": "Newest", "newest": "Newest",
@ -55,51 +104,14 @@
"email": "Your Email", "email": "Your Email",
"password": "Your Password", "password": "Your Password",
"forgotPassword": "Forgot Password?", "forgotPassword": "Forgot Password?",
"no-account": "Don't have an account?",
"register": "Sign up",
"moreInfo": "What is Human Connection?", "moreInfo": "What is Human Connection?",
"moreInfoURL": "https://human-connection.org/en/", "moreInfoURL": "https://human-connection.org/en/",
"moreInfoHint": "to the presentation page", "moreInfoHint": "to the presentation page",
"hello": "Hello", "hello": "Hello",
"success": "You are logged in!" "success": "You are logged in!"
}, },
"password-reset": {
"title": "Reset your password",
"form": {
"description": "A password reset email will be sent to the given email address.",
"submit": "Request email",
"submitted": "An email with further instructions has been sent to <b>{email}</b>"
}
},
"registration": {
"signup": {
"title": "Join Human Connection!",
"form": {
"description": "To get started, enter your email address:",
"invitation-code": "Your invitation code is: <b>{code}</b>",
"errors": {
"email-exists": "There is already a user account with this email address!",
"invalid-invitation-token": "It looks like as if the invitation has been used already. Invitation links can only be used once."
},
"submit": "Create an account",
"success": "A mail with a link to complete your registration has been sent to <b>{email}</b>"
}
},
"create-user-account": {
"title": "Create user account",
"success": "Your account has been created!"
}
},
"verify-nonce": {
"form": {
"nonce": "Enter your code",
"description": "Open your inbox and enter the code that we've sent to you.",
"next": "Continue",
"change-password": {
"success": "Changing your password was successful!",
"error": "Changing your password failed. Maybe the security code was not correct?",
"help": "In case of problems, feel free to ask for help by sending us a mail to:"
}
}
},
"editor": { "editor": {
"placeholder": "Leave your inspirational thoughts …", "placeholder": "Leave your inspirational thoughts …",
"mention": { "mention": {
@ -401,8 +413,7 @@
"reportContent": "Report", "reportContent": "Report",
"validations": { "validations": {
"email": "must be a valid email address", "email": "must be a valid email address",
"url": "must be a valid URL", "url": "must be a valid URL"
"verification-nonce": "must be 6 characters long"
} }
}, },
"actions": { "actions": {

View File

@ -1,4 +1,31 @@
{ {
"components": {
"password-reset": {
"request": {
"title": "Zresetuj hasło",
"form": {
"description": "Na podany adres e-mail zostanie wysłany email z resetem hasła.",
"submit": "Poproś o wiadomość e-mail",
"submitted": "Na adres <b>{email}</b> została wysłana wiadomość z dalszymi instrukcjami."
}
},
"change-password": {
"success": "Zmiana hasła zakończyła się sukcesem!",
"error": "Zmiana hasła nie powiodła się. Może kod bezpieczeństwa nie był poprawny?",
"help": "W przypadku problemów, zachęcamy do zwrócenia się o pomoc, wysyłając do nas wiadomość e-mail:"
}
},
"enter-nonce": {
"form": {
"nonce": "Wprowadź swój kod",
"description": "Otwórz swoją skrzynkę odbiorczą i wpisz kod, który do Ciebie wysłaliśmy.",
"next": "Kontynuuj",
"validations": {
"length": "musi mieć długość 6 znaków."
}
}
}
},
"filter-menu": { "filter-menu": {
"title": "Twoja bańka filtrująca" "title": "Twoja bańka filtrująca"
}, },
@ -29,26 +56,6 @@
"moreInfoHint": "idź po więcej szczegółów", "moreInfoHint": "idź po więcej szczegółów",
"hello": "Cześć" "hello": "Cześć"
}, },
"password-reset": {
"title": "Zresetuj hasło",
"form": {
"description": "Na podany adres e-mail zostanie wysłany email z resetem hasła.",
"submit": "Poproś o wiadomość e-mail",
"submitted": "Na adres <b>{email}</b> została wysłana wiadomość z dalszymi instrukcjami."
}
},
"verify-nonce": {
"form": {
"nonce": "Wprowadź swój kod",
"description": "Otwórz swoją skrzynkę odbiorczą i wpisz kod, który do Ciebie wysłaliśmy.",
"next": "Kontynuuj",
"change-password": {
"success": "Zmiana hasła zakończyła się sukcesem!",
"error": "Zmiana hasła nie powiodła się. Może kod bezpieczeństwa nie był poprawny?",
"help": "W przypadku problemów, zachęcamy do zwrócenia się o pomoc, wysyłając do nas wiadomość e-mail:"
}
}
},
"editor": { "editor": {
"placeholder": "Napisz coś inspirującego..." "placeholder": "Napisz coś inspirującego..."
}, },
@ -236,8 +243,7 @@
"loading": "załadunek", "loading": "załadunek",
"reportContent": "Sprawozdanie", "reportContent": "Sprawozdanie",
"validations": { "validations": {
"email": "musi być ważny adres e-mail.", "email": "musi być ważny adres e-mail."
"verification-nonce": "musi mieć długość 6 znaków."
} }
}, },
"actions": { "actions": {

View File

@ -1,6 +1,10 @@
import path from 'path' import path from 'path'
import dotenv from 'dotenv'
dotenv.config() // we want to synchronize @nuxt-dotenv and nuxt-env
const pkg = require('./package') const pkg = require('./package')
export const envWhitelist = ['NODE_ENV', 'MAPBOX_TOKEN'] export const envWhitelist = ['NODE_ENV', 'MAPBOX_TOKEN', 'PUBLIC_REGISTRATION']
const dev = process.env.NODE_ENV !== 'production' const dev = process.env.NODE_ENV !== 'production'
const styleguidePath = '../styleguide' const styleguidePath = '../styleguide'
@ -36,11 +40,10 @@ export default {
'login', 'login',
'logout', 'logout',
'password-reset-request', 'password-reset-request',
'password-reset-verify-nonce', 'password-reset-enter-nonce',
'password-reset-change-password', 'password-reset-change-password',
// 'registration-signup', TODO: implement to open public registration 'registration-signup',
// 'registration-signup-by-invitation-code', 'registration-enter-nonce',
// 'registration-verify-nonce',
'registration-create-user-account', 'registration-create-user-account',
'pages-slug', 'pages-slug',
'terms-and-conditions', 'terms-and-conditions',

View File

@ -5,7 +5,9 @@
<ds-heading size="h3">{{ $t('admin.invites.title') }}</ds-heading> <ds-heading size="h3">{{ $t('admin.invites.title') }}</ds-heading>
<ds-text>{{ $t('admin.invites.description') }}</ds-text> <ds-text>{{ $t('admin.invites.description') }}</ds-text>
</ds-space> </ds-space>
<signup :invitation="true" /> <ds-card class="signup">
<signup :invitation="true" />
</ds-card>
</ds-section> </ds-section>
</template> </template>

View File

@ -22,6 +22,11 @@
</ds-space> </ds-space>
</ds-flex-item> </ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '50%' }" centered> <ds-flex-item :width="{ base: '100%', sm: '50%' }" centered>
<ds-space margin="small">
<a :href="$t('login.moreInfoURL')" :title="$t('login.moreInfoHint')" target="_blank">
{{ $t('login.moreInfo') }}
</a>
</ds-space>
<ds-space margin="small"> <ds-space margin="small">
<ds-text size="small">{{ $t('login.copy') }}</ds-text> <ds-text size="small">{{ $t('login.copy') }}</ds-text>
</ds-space> </ds-space>
@ -43,7 +48,7 @@
name="password" name="password"
type="password" type="password"
/> />
<ds-space class="password-reset-link" margin-bottom="large"> <ds-space margin-bottom="large">
<nuxt-link to="/password-reset/request">{{ $t('login.forgotPassword') }}</nuxt-link> <nuxt-link to="/password-reset/request">{{ $t('login.forgotPassword') }}</nuxt-link>
</ds-space> </ds-space>
<ds-button <ds-button
@ -56,14 +61,9 @@
> >
{{ $t('login.login') }} {{ $t('login.login') }}
</ds-button> </ds-button>
<ds-space margin="x-small"> <ds-space margin-top="large" margin-bottom="x-small">
<a {{ $t('login.no-account') }}
:href="$t('login.moreInfoURL')" <nuxt-link to="/registration/signup">{{ $t('login.register') }}</nuxt-link>
:title="$t('login.moreInfoHint')"
target="_blank"
>
{{ $t('login.moreInfo') }}
</a>
</ds-space> </ds-space>
</form> </form>
</ds-flex-item> </ds-flex-item>

View File

@ -1,17 +1,30 @@
<template> <template>
<ds-container width="medium"> <ds-container width="small">
<ds-flex> <ds-card>
<ds-flex-item :width="{ base: '100%' }" centered> <ds-flex gutter="small">
<ds-space style="text-align: center;" margin-top="small" margin-bottom="xxx-small" centered> <ds-flex-item :width="{ base: '100%', sm: '40%' }">
<client-only>
<locale-switch offset="5" />
</client-only>
<ds-space margin-top="small" margin-bottom="xxx-small" centered>
<img alt="Human Connection" src="/icon.png" />
</ds-space>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '60%' }" centered>
<nuxt-child /> <nuxt-child />
</ds-space> </ds-flex-item>
</ds-flex-item> </ds-flex>
</ds-flex> </ds-card>
</ds-container> </ds-container>
</template> </template>
<script> <script>
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
export default { export default {
components: {
LocaleSwitch,
},
layout: 'no-header', layout: 'no-header',
asyncData({ store, redirect }) { asyncData({ store, redirect }) {
if (store.getters['auth/isLoggedIn']) { if (store.getters['auth/isLoggedIn']) {
@ -20,3 +33,10 @@ export default {
}, },
} }
</script> </script>
<style lang="scss" scoped>
img {
padding-left: 50px;
padding-right: 50px;
max-width: 200px;
}
</style>

View File

@ -3,7 +3,11 @@
:email="email" :email="email"
:nonce="nonce" :nonce="nonce"
@passwordResetResponse="handlePasswordResetResponse" @passwordResetResponse="handlePasswordResetResponse"
/> >
<ds-space centered>
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
</ds-space>
</change-password>
</template> </template>
<script> <script>

View File

@ -0,0 +1,26 @@
<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: 'change-password', query: { email, nonce } })
},
},
}
</script>

View File

@ -1,5 +1,9 @@
<template> <template>
<request @handleSubmitted="handlePasswordResetRequested" /> <request @handleSubmitted="handlePasswordResetRequested">
<ds-space margin-bottom="xxx-small" margin-top="large" centered>
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
</ds-space>
</request>
</template> </template>
<script> <script>
@ -11,7 +15,7 @@ export default {
}, },
methods: { methods: {
handlePasswordResetRequested({ email }) { handlePasswordResetRequested({ email }) {
this.$router.push({ path: 'verify-nonce', query: { email } }) this.$router.push({ path: 'enter-nonce', query: { email } })
}, },
}, },
} }

View File

@ -1,22 +0,0 @@
<template>
<verify-nonce :email="email" @verification="handleVerification" />
</template>
<script>
import VerifyNonce from '~/components/PasswordReset/VerifyNonce'
export default {
components: {
VerifyNonce,
},
data() {
const { email = '' } = this.$route.query
return { email }
},
methods: {
handleVerification({ email, nonce }) {
this.$router.push({ path: 'change-password', query: { email, nonce } })
},
},
}
</script>

View File

@ -1,9 +1,29 @@
<template> <template>
<nuxt-child /> <ds-container width="medium">
<ds-card>
<ds-flex gutter="small">
<ds-flex-item :width="{ base: '100%', sm: '50%' }">
<client-only>
<locale-switch offset="5" />
</client-only>
<ds-space margin-top="small" margin-bottom="xxx-small">
<img class="signup-image" alt="Human Connection" src="/img/sign-up/nicetomeetyou.svg" />
</ds-space>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '50%' }" centered>
<nuxt-child />
</ds-flex-item>
</ds-flex>
</ds-card>
</ds-container>
</template> </template>
<script> <script>
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
export default { export default {
components: {
LocaleSwitch,
},
layout: 'no-header', layout: 'no-header',
asyncData({ store, redirect }) { asyncData({ store, redirect }) {
if (store.getters['auth/isLoggedIn']) { if (store.getters['auth/isLoggedIn']) {

View File

@ -0,0 +1,25 @@
<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>

View File

@ -0,0 +1,34 @@
<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.vue'
export default {
layout: 'no-header',
components: {
HcEmpty,
Signup,
},
asyncData({ app }) {
return {
publicRegistration: app.$env.PUBLIC_REGISTRATION === 'true',
}
},
methods: {
handleSubmitted({ email }) {
this.$router.push({ path: 'enter-nonce', query: { email } })
},
},
}
</script>