Implement registration slider e-mail, nonce, and storybook with preset data

This commit is contained in:
Wolfgang Huß 2021-02-09 13:31:59 +01:00
parent e3d3f988ce
commit 9086f99862
6 changed files with 457 additions and 172 deletions

View File

@ -29,7 +29,8 @@
</ds-heading> </ds-heading>
</ds-space> --> </ds-space> -->
<ds-form class="create-user-account" <ds-form
class="create-user-account"
v-model="formData" v-model="formData"
:schema="formSchema" :schema="formSchema"
@submit="submit" @submit="submit"
@ -226,6 +227,7 @@ export default {
this.noPolitical = this.sliderData.collectedInputData.noPolitical this.noPolitical = this.sliderData.collectedInputData.noPolitical
? this.sliderData.collectedInputData.noPolitical ? this.sliderData.collectedInputData.noPolitical
: false : false
this.sendValidation()
}) })
}, },
computed: { computed: {
@ -284,7 +286,7 @@ export default {
noCommercial, noCommercial,
noPolitical, noPolitical,
} = this } = this
// const locale = this.$i18n.locale() const locale = this.$i18n.locale()
const value = { const value = {
name, name,
about, about,
@ -298,7 +300,7 @@ export default {
minimumAge, minimumAge,
noCommercial, noCommercial,
noPolitical, noPolitical,
// locale, locale,
} }
console.log('sendValidation !!!', ' this.valid: ', this.valid, ' value: ', value) console.log('sendValidation !!!', ' this.valid: ', this.valid, ' value: ', value)
this.sliderData.validateCallback(this.valid, value) this.sliderData.validateCallback(this.valid, value)

View File

@ -0,0 +1,197 @@
<template>
<ds-space v-if="!data && !error" margin="large">
<ds-form
v-model="formData"
:schema="formSchema"
@input="handleInput"
@input-valid="handleInputValid"
@submit="handleSubmit"
>
<!-- Wolle <h1>
{{
invitation
? $t('profile.invites.title', metadata)
: $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>
{{
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"
/>
<!-- <base-button
:disabled="disabled"
:loading="$apollo.loading"
filled
name="submit"
type="submit"
icon="envelope"
>
{{ $t('components.registration.signup.form.submit') }}
</base-button> -->
<slot></slot>
</ds-form>
</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 { 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: {
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'),
},
// email: [
// { type: 'email', required: true },
// {
// validator(rule, value, callback, source, options) {
// const errors = []
// if (currentEmail === normalizeEmail(value)) {
// errors.push(this.$t('common.validations.email'))
// }
// return errors
// },
// },
// ],
},
// disabled: true,
data: null,
error: null,
}
},
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the entire view has been rendered
// console.log('mounted !!! ')
this.formData.email = this.sliderData.collectedInputData.email
? this.sliderData.collectedInputData.email
: ''
})
},
computed: {
submitMessage() {
const { email } = this.data.Signup
return this.$t('components.registration.signup.form.success', { email })
},
// valid() {
// const isValid =
// this.formData.email.XXX === XXX
// return isValid
// },
},
methods: {
handleInput() {
// this.disabled = true
this.sliderData.validateCallback(false)
},
handleInputValid() {
// this.disabemailled = false
const { email } = this.formData
// validate in backend?
// toaster?
console.log('sendValidation !!! email: ', email)
this.sliderData.validateCallback(true, { email })
},
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 } })
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)
}
}
},
},
}
</script>
<style>
.space-top {
margin-top: 6ex;
}
</style>

View File

@ -3,9 +3,9 @@
class="enter-invite" class="enter-invite"
v-model="formData" v-model="formData"
:schema="formSchema" :schema="formSchema"
@submit="handleSubmitVerify"
@input="handleInput" @input="handleInput"
@input-valid="handleInputValid" @input-valid="handleInputValid"
@submit="handleSubmitVerify"
> >
<ds-input <ds-input
:placeholder="$t('components.enter-invite.form.invite-code')" :placeholder="$t('components.enter-invite.form.invite-code')"
@ -46,23 +46,41 @@ export default {
mounted: function () { mounted: function () {
this.$nextTick(function () { this.$nextTick(function () {
// Code that will run only after the entire view has been rendered // Code that will run only after the entire view has been rendered
// console.log('mounted !!! ') console.log('mounted !!! this.sliderData.collectedInputData.inviteCode: ', this.sliderData.collectedInputData.inviteCode)
this.formData.inviteCode = this.sliderData.collectedInputData.inviteCode this.formData.inviteCode = this.sliderData.collectedInputData.inviteCode
? this.sliderData.collectedInputData.inviteCode ? this.sliderData.collectedInputData.inviteCode
: '' : ''
this.sendValidation()
}) })
}, },
computed: {
valid() {
const isValid =
this.formData.inviteCode.length === 6
return isValid
},
},
methods: { methods: {
sendValidation() {
const { inviteCode } = this.formData
const value = {
inviteCode,
}
console.log('sendValidation !!! value: ', value)
this.sliderData.validateCallback(this.valid, value)
},
async handleInput() { async handleInput() {
// Wolle console.log('handleInput !!!') // Wolle console.log('handleInput !!!')
this.sliderData.validateCallback(false) // this.sliderData.validateCallback(false)
this.sendValidation()
}, },
async handleInputValid() { async handleInputValid() {
// Wolle console.log('handleInputValid !!!') // Wolle console.log('handleInputValid !!!')
const { inviteCode } = this.formData // const { inviteCode } = this.formData
// validate in backend // validate in backend
// toaster // toaster
this.sliderData.validateCallback(true, { /* email, */ inviteCode }) // this.sliderData.validateCallback(true, { /* email, */ inviteCode })
this.sendValidation()
}, },
handleSubmitVerify() { handleSubmitVerify() {
// Wolle const { nonce } = this.formData // Wolle const { nonce } = this.formData

View File

@ -0,0 +1,86 @@
<template>
<ds-form
class="enter-nonce"
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-text>
{{ $t('components.enter-nonce.form.description') }}
</ds-text>
<!-- <base-button :disabled="disabled" filled name="submit" type="submit">
{{ $t('components.enter-nonce.form.next') }}
</base-button> -->
<slot></slot>
</ds-form>
</template>
<script>
export default {
name: 'RegistrationItemEnterNonce',
props: {
sliderData: { type: Object, 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,
}
},
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
: ''
})
},
methods: {
async handleInput() {
// this.disabled = true
this.sliderData.validateCallback(false)
},
async handleInputValid() {
// this.disabled = false
const { nonce } = this.formData
// validate in backend?
// toaster?
this.sliderData.validateCallback(true, { nonce })
},
handleSubmitVerify() {
// const { nonce } = this.formData
// const email = this.email
// this.$emit('nonceEntered', { email, nonce })
},
},
}
</script>
<style lang="scss">
.enter-nonce {
display: flex;
flex-direction: column;
margin: $space-large 0 $space-xxx-small 0;
}
</style>

View File

@ -5,123 +5,63 @@ import helpers from '~/storybook/helpers'
helpers.init() helpers.init()
// Wolle export const searchResults = [
// {
// id: 'post-by-jenny',
// __typename: 'Post',
// slug: 'user-post-by-jenny',
// title: 'User Post by Jenny',
// value: 'User Post by Jenny',
// shoutedCount: 0,
// commentsCount: 4,
// createdAt: '2019-11-13T03:03:16.155Z',
// author: {
// id: 'u3',
// name: 'Jenny Rostock',
// slug: 'jenny-rostock',
// },
// },
// {
// id: 'f48f00a0-c412-432f-8334-4276a4e15d1c',
// __typename: 'Post',
// slug: 'eum-quos-est-molestiae-enim-magni-consequuntur-sed-commodi-eos',
// title: 'Eum quos est molestiae enim magni consequuntur sed commodi eos.',
// value: 'Eum quos est molestiae enim magni consequuntur sed commodi eos.',
// shoutedCount: 0,
// commentsCount: 0,
// createdAt: '2019-11-13T03:00:45.478Z',
// author: {
// id: 'u6',
// name: 'Louie',
// slug: 'louie',
// },
// },
// {
// id: 'p7',
// __typename: 'Post',
// slug: 'this-is-post-7',
// title: 'This is post #7',
// value: 'This is post #7',
// shoutedCount: 1,
// commentsCount: 1,
// createdAt: '2019-11-13T03:00:23.098Z',
// author: {
// id: 'u6',
// name: 'Louie',
// slug: 'louie',
// },
// },
// {
// id: 'p12',
// __typename: 'Post',
// slug: 'this-is-post-12',
// title: 'This is post #12',
// value: 'This is post #12',
// shoutedCount: 0,
// commentsCount: 12,
// createdAt: '2019-11-13T03:00:23.098Z',
// author: {
// id: 'u6',
// name: 'Louie',
// slug: 'louie',
// },
// },
// {
// id: 'u1',
// __typename: 'User',
// avatar: {
// url:
// 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
// },
// name: 'Peter Lustig',
// slug: 'peter-lustig',
// },
// {
// id: 'cdbca762-0632-4564-b646-415a0c42d8b8',
// __typename: 'User',
// avatar: {
// url:
// 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
// },
// name: 'Herbert Schultz',
// slug: 'herbert-schultz',
// },
// {
// id: 'u2',
// __typename: 'User',
// avatar: {
// url:
// 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
// },
// name: 'Bob der Baumeister',
// slug: 'bob-der-baumeister',
// },
// {
// id: '7b654f72-f4da-4315-8bed-39de0859754b',
// __typename: 'User',
// avatar: {
// url:
// 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
// },
// name: 'Tonya Mohr',
// slug: 'tonya-mohr',
// },
// {
// id: 'Hashtag',
// __typename: 'Tag',
// },
// ]
storiesOf('RegistrationSlider', module) storiesOf('RegistrationSlider', module)
.addDecorator(withA11y) .addDecorator(withA11y)
.addDecorator(helpers.layout) .addDecorator(helpers.layout)
.add('standard', () => ({ .add('invite-code empty', () => ({
components: { RegistrationSlider }, components: { RegistrationSlider },
store: helpers.store, store: helpers.store,
data: () => ({ data: () => ({
// Wolle searchResults, // Wolle searchResults,
}), }),
template: ` template: `
<registration-slider /> <registration-slider registrationType="invite-code" />
`,
}))
.add('invite-code with data', () => ({
components: { RegistrationSlider },
store: helpers.store,
data: () => ({
overwriteSliderData: {
collectedInputData: {
inviteCode: 'IN1T6Y',
email: 'wolle.huss@pjannto.com',
nonce: 'NTRSCZ',
name: 'Wolle',
password: 'Hello',
passwordConfirmation: 'Hello',
about: `Hey`,
termsAndConditionsAgreedVersion: null,
termsAndConditionsConfirmed: true,
dataPrivacy: true,
minimumAge: true,
noCommercial: true,
noPolitical: true,
locale: null,
},
},
}),
template: `
<registration-slider registrationType="invite-code" :overwriteSliderData="overwriteSliderData" />
`,
}))
.add('public-registration', () => ({
components: { RegistrationSlider },
store: helpers.store,
data: () => ({
// Wolle searchResults,
}),
template: `
<registration-slider registrationType="public-registration" />
`,
}))
.add('invite-mail', () => ({
components: { RegistrationSlider },
store: helpers.store,
data: () => ({
// Wolle searchResults,
}),
template: `
<registration-slider registrationType="invite-mail" />
`, `,
})) }))

View File

@ -14,23 +14,25 @@
</ds-heading> </ds-heading>
</template> </template>
<template #enter-invite> <template v-if="['invite-code'].includes(registrationType)" #enter-invite>
<registration-item-enter-invite :sliderData="sliderData" /> <registration-item-enter-invite :sliderData="sliderData" />
</template> </template>
<!-- <template #enter-email> --> <template
v-if="['invite-code', 'public-registration'].includes(registrationType)"
#enter-email
>
<!-- Wolle !!! may create same source with 'webapp/pages/registration/signup.vue' --> <!-- Wolle !!! may create same source with 'webapp/pages/registration/signup.vue' -->
<!-- <signup v-if="publicRegistration" :invitation="false" @submit="handleSubmitted"> --> <!-- <signup v-if="publicRegistration" :invitation="false" @submit="handleSubmitted"> -->
<!-- <signup :invitation="false" @submit="handleSubmitted"> <registration-item-enter-email :invitation="false" :sliderData="sliderData" />
<ds-space centered margin-top="large"> </template>
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
</ds-space> <template
</signup> --> v-if="['invite-code', 'public-registration', 'invite-mail'].includes(registrationType)"
<!-- <ds-space v-else centered> #enter-nonce
<hc-empty icon="events" :message="$t('components.registration.signup.unavailable')" /> >
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link> <registration-item-enter-nonce :sliderData="sliderData" />
</ds-space> --> </template>
<!-- </template> -->
<template #create-user-account> <template #create-user-account>
<registration-item-create-user-account <registration-item-create-user-account
@ -58,11 +60,12 @@
import links from '~/constants/links.js' import links from '~/constants/links.js'
import metadata from '~/constants/metadata.js' import metadata from '~/constants/metadata.js'
import ComponentSlider from '~/components/ComponentSlider/ComponentSlider' import ComponentSlider from '~/components/ComponentSlider/ComponentSlider'
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import Signup from '~/components/Registration/Signup'
import HcEmpty from '~/components/Empty/Empty' import HcEmpty from '~/components/Empty/Empty'
import RegistrationItemEnterInvite from './RegistrationItemEnterInvite' import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import RegistrationItemCreateUserAccount from './RegistrationItemCreateUserAccount' import RegistrationItemCreateUserAccount from './RegistrationItemCreateUserAccount'
import RegistrationItemEnterEmail from '~/components/Registration/RegistrationItemEnterEmail'
import RegistrationItemEnterInvite from './RegistrationItemEnterInvite'
import RegistrationItemEnterNonce from './RegistrationItemEnterNonce'
export default { export default {
name: 'RegistrationSlider', name: 'RegistrationSlider',
@ -70,32 +73,17 @@ export default {
ComponentSlider, ComponentSlider,
HcEmpty, HcEmpty,
LocaleSwitch, LocaleSwitch,
RegistrationItemEnterInvite,
RegistrationItemCreateUserAccount, RegistrationItemCreateUserAccount,
Signup, RegistrationItemEnterEmail,
RegistrationItemEnterInvite,
RegistrationItemEnterNonce,
},
props: {
registrationType: { type: String, required: true },
overwriteSliderData: { type: Object, default: () => {} },
}, },
data() { data() {
return { const slidersPortfolio = [
links,
metadata,
sliderData: {
collectedInputData: {
inviteCode: null,
email: null,
nonce: null,
name: null,
// password: null,
about: null,
termsAndConditionsAgreedVersion: null,
termsAndConditionsConfirmed: null,
dataPrivacy: null,
minimumAge: null,
noCommercial: null,
noPolitical: null,
// locale: null, // Wolle: what to do? Is collected in the last slider?! and gets stored in the database
},
sliderIndex: 0,
sliders: [
{ {
name: 'enter-invite', name: 'enter-invite',
// title: this.$t('components.registration.create-user-account.title'), // title: this.$t('components.registration.create-user-account.title'),
@ -107,29 +95,83 @@ export default {
callback: this.buttonCallback, callback: this.buttonCallback,
}, },
}, },
// { {
// name: 'enter-email', name: 'enter-email',
// validated: false, title: 'E-Mail', // Wolle
// title: 'E-Mail', // Wolle validated: false,
// button: { button: {
// title: 'Next', // Wolle title: 'Send E-Mail', // Wolle
// icon: :icon="(sliderIndex < sliderData.sliders.length - 1 && 'arrow-right') || (sliderIndex === sliderData.sliders.length - 1 && 'check')" icon: 'envelope',
// callback: this.buttonCallback, callback: this.buttonCallback,
// }, },
// }, },
{
name: 'enter-nonce',
title: 'E-Mail Confirmation', // Wolle
validated: false,
button: {
title: 'Confirm', // Wolle
icon: 'arrow-right',
callback: this.buttonCallback,
},
},
{ {
name: 'create-user-account', name: 'create-user-account',
title: this.$t('components.registration.create-user-account.title'), title: this.$t('components.registration.create-user-account.title'),
validated: false, validated: false,
button: { button: {
title: this.$t('actions.save'), // Wolle // title: this.$t('actions.save'), // Wolle
title: 'Create', // Wolle
icon: 'check', icon: 'check',
callback: this.buttonCallback, callback: this.buttonCallback,
}, },
}, },
], ]
// Wolle console.log('this.registrationType: ', this.registrationType)
let sliders = []
switch (this.registrationType) {
case 'invite-code':
sliders = [
slidersPortfolio[0],
slidersPortfolio[1],
slidersPortfolio[2],
slidersPortfolio[3],
]
break
case 'public-registration':
sliders = [slidersPortfolio[1], slidersPortfolio[2], slidersPortfolio[3]]
break
case 'invite-mail':
sliders = [slidersPortfolio[2], slidersPortfolio[3]]
break
}
// let sliders = slidersPortfolio
return {
links,
metadata,
sliderData: {
collectedInputData: {
inviteCode: null,
email: null,
nonce: null,
name: null,
password: null,
passwordConfirmation: null,
about: null,
termsAndConditionsAgreedVersion: null,
termsAndConditionsConfirmed: null,
dataPrivacy: null,
minimumAge: null,
noCommercial: null,
noPolitical: null,
locale: null, // Wolle: what to do? Is collected in the last slider?! and gets stored in the database
},
sliderIndex: 0,
sliders: sliders,
sliderSelectorCallback: this.sliderSelectorCallback, sliderSelectorCallback: this.sliderSelectorCallback,
validateCallback: this.validateCallback, validateCallback: this.validateCallback,
...this.overwriteSliderData,
}, },
} }
}, },